4568 lines
163 KiB
Zig
4568 lines
163 KiB
Zig
const std = @import("std");
|
|
const Ast = @import("Ast.zig");
|
|
const Air = @import("Air.zig");
|
|
const ErrorList = @import("ErrorList.zig");
|
|
const TokenTag = @import("Token.zig").Tag;
|
|
const Loc = @import("Token.zig").Loc;
|
|
const Inst = Air.Inst;
|
|
const InstIndex = Air.InstIndex;
|
|
const RefIndex = Air.RefIndex;
|
|
const StringIndex = Air.StringIndex;
|
|
const ValueIndex = Air.ValueIndex;
|
|
const Node = Ast.Node;
|
|
const NodeIndex = Ast.NodeIndex;
|
|
const TokenIndex = Ast.TokenIndex;
|
|
const stringToEnum = std.meta.stringToEnum;
|
|
const indexOf = std.mem.indexOfScalar;
|
|
|
|
const AstGen = @This();
|
|
|
|
allocator: std.mem.Allocator,
|
|
tree: *const Ast,
|
|
instructions: std.AutoArrayHashMapUnmanaged(Inst, void) = .{},
|
|
refs: std.ArrayListUnmanaged(InstIndex) = .{},
|
|
strings: std.ArrayListUnmanaged(u8) = .{},
|
|
values: std.ArrayListUnmanaged(u8) = .{},
|
|
scratch: std.ArrayListUnmanaged(InstIndex) = .{},
|
|
global_var_refs: std.AutoArrayHashMapUnmanaged(InstIndex, void) = .{},
|
|
globals: std.ArrayListUnmanaged(InstIndex) = .{},
|
|
has_array_length: bool = false,
|
|
compute_stage: InstIndex = .none,
|
|
vertex_stage: InstIndex = .none,
|
|
fragment_stage: InstIndex = .none,
|
|
entry_point_name: ?[]const u8 = null,
|
|
scope_pool: std.heap.MemoryPool(Scope),
|
|
current_fn_scope: *Scope = undefined,
|
|
inst_arena: std.heap.ArenaAllocator,
|
|
errors: *ErrorList,
|
|
|
|
pub const Scope = struct {
|
|
tag: Tag,
|
|
/// this is undefined if tag == .root
|
|
parent: *Scope,
|
|
decls: std.AutoHashMapUnmanaged(NodeIndex, error{AnalysisFail}!InstIndex) = .{},
|
|
|
|
const Tag = union(enum) {
|
|
root,
|
|
@"fn": struct {
|
|
stage: Inst.Fn.Stage,
|
|
return_type: InstIndex,
|
|
returned: bool,
|
|
flattened_params: std.AutoHashMapUnmanaged(InstIndex, InstIndex),
|
|
},
|
|
block,
|
|
loop,
|
|
continuing,
|
|
switch_case,
|
|
@"if",
|
|
@"for",
|
|
};
|
|
};
|
|
|
|
pub fn genTranslationUnit(astgen: *AstGen) !RefIndex {
|
|
var root_scope = try astgen.scope_pool.create();
|
|
root_scope.* = .{ .tag = .root, .parent = undefined };
|
|
|
|
const global_nodes = astgen.tree.spanToList(.globals);
|
|
try astgen.scanDecls(root_scope, global_nodes);
|
|
|
|
for (global_nodes) |node| {
|
|
var global = root_scope.decls.get(node).? catch continue;
|
|
global = switch (astgen.tree.nodeTag(node)) {
|
|
.@"fn" => blk: {
|
|
break :blk astgen.genFn(root_scope, node, false) catch |err| switch (err) {
|
|
error.Skiped => continue,
|
|
else => |e| e,
|
|
};
|
|
},
|
|
else => continue,
|
|
} catch |err| {
|
|
if (err == error.AnalysisFail) {
|
|
root_scope.decls.putAssumeCapacity(node, error.AnalysisFail);
|
|
continue;
|
|
}
|
|
return err;
|
|
};
|
|
root_scope.decls.putAssumeCapacity(node, global);
|
|
try astgen.globals.append(astgen.allocator, global);
|
|
}
|
|
|
|
if (astgen.errors.list.items.len > 0) return error.AnalysisFail;
|
|
|
|
if (astgen.entry_point_name != null and
|
|
astgen.compute_stage == .none and
|
|
astgen.vertex_stage == .none and
|
|
astgen.fragment_stage == .none)
|
|
{
|
|
try astgen.errors.add(Loc{ .start = 0, .end = 1 }, "entry point not found", .{}, null);
|
|
}
|
|
|
|
return astgen.addRefList(astgen.globals.items);
|
|
}
|
|
|
|
/// adds `nodes` to scope and checks for re-declarations
|
|
fn scanDecls(astgen: *AstGen, scope: *Scope, nodes: []const NodeIndex) !void {
|
|
for (nodes) |decl_node| {
|
|
const loc = astgen.tree.declNameLoc(decl_node) orelse continue;
|
|
const name = loc.slice(astgen.tree.source);
|
|
|
|
var iter = scope.decls.keyIterator();
|
|
while (iter.next()) |node| {
|
|
const name_loc = astgen.tree.declNameLoc(node.*).?;
|
|
if (std.mem.eql(u8, name, name_loc.slice(astgen.tree.source))) {
|
|
try astgen.errors.add(
|
|
loc,
|
|
"redeclaration of '{s}'",
|
|
.{name},
|
|
try astgen.errors.createNote(
|
|
name_loc,
|
|
"other declaration here",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
try scope.decls.putNoClobber(astgen.scope_pool.arena.allocator(), decl_node, .none);
|
|
}
|
|
}
|
|
|
|
fn genGlobalDecl(astgen: *AstGen, scope: *Scope, node: NodeIndex) error{ OutOfMemory, AnalysisFail }!InstIndex {
|
|
const decl = switch (astgen.tree.nodeTag(node)) {
|
|
.global_var => astgen.genGlobalVar(scope, node),
|
|
.override => astgen.genOverride(scope, node),
|
|
.@"const" => astgen.genConst(scope, node),
|
|
.@"struct" => astgen.genStruct(scope, node),
|
|
.@"fn" => astgen.genFn(scope, node, false),
|
|
.type_alias => astgen.genTypeAlias(scope, node),
|
|
else => unreachable,
|
|
} catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
scope.decls.putAssumeCapacity(node, error.AnalysisFail);
|
|
return error.AnalysisFail;
|
|
},
|
|
error.Skiped => unreachable,
|
|
else => |e| return e,
|
|
};
|
|
|
|
scope.decls.putAssumeCapacity(node, decl);
|
|
try astgen.globals.append(astgen.allocator, decl);
|
|
return decl;
|
|
}
|
|
|
|
fn genGlobalVar(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const extra = astgen.tree.extraData(Node.GlobalVar, astgen.tree.nodeLHS(node));
|
|
const name_loc = astgen.tree.declNameLoc(node).?;
|
|
|
|
var is_resource = false;
|
|
var var_type = InstIndex.none;
|
|
if (extra.type != .none) {
|
|
var_type = try astgen.genType(scope, extra.type);
|
|
|
|
switch (astgen.getInst(var_type)) {
|
|
.sampler_type,
|
|
.comparison_sampler_type,
|
|
.external_texture_type,
|
|
.texture_type,
|
|
=> {
|
|
is_resource = true;
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
var addr_space = Inst.PointerType.AddressSpace.uniform_constant;
|
|
if (extra.addr_space != .none) {
|
|
const addr_space_loc = astgen.tree.tokenLoc(extra.addr_space);
|
|
const ast_addr_space = stringToEnum(Ast.AddressSpace, addr_space_loc.slice(astgen.tree.source)).?;
|
|
addr_space = switch (ast_addr_space) {
|
|
.function => .function,
|
|
.private => .private,
|
|
.workgroup => .workgroup,
|
|
.uniform => .uniform,
|
|
.storage => .storage,
|
|
};
|
|
}
|
|
|
|
if (addr_space == .uniform or addr_space == .storage) {
|
|
is_resource = true;
|
|
}
|
|
|
|
var access_mode = Inst.PointerType.AccessMode.read_write;
|
|
if (extra.access_mode != .none) {
|
|
const access_mode_loc = astgen.tree.tokenLoc(extra.access_mode);
|
|
const ast_access_mode = stringToEnum(Ast.AccessMode, access_mode_loc.slice(astgen.tree.source)).?;
|
|
access_mode = switch (ast_access_mode) {
|
|
.read => .read,
|
|
.write => .write,
|
|
.read_write => .read_write,
|
|
};
|
|
}
|
|
|
|
var binding = InstIndex.none;
|
|
var group = InstIndex.none;
|
|
if (extra.attrs != .none) {
|
|
for (astgen.tree.spanToList(extra.attrs)) |attr| {
|
|
if (!is_resource) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"variable '{s}' is not a resource",
|
|
.{name_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (astgen.tree.nodeTag(attr)) {
|
|
.attr_binding => binding = try astgen.attrBinding(scope, attr),
|
|
.attr_group => group = try astgen.attrGroup(scope, attr),
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"unexpected attribute '{s}'",
|
|
.{astgen.tree.nodeLoc(attr).slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_resource and (binding == .none or group == .none)) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node),
|
|
"resource variable must specify binding and group",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
var init = InstIndex.none;
|
|
if (node_rhs != .none) {
|
|
init = try astgen.genExpr(scope, node_rhs);
|
|
}
|
|
|
|
const name = try astgen.addString(name_loc.slice(astgen.tree.source));
|
|
return astgen.addInst(.{
|
|
.@"var" = .{
|
|
.name = name,
|
|
.type = var_type,
|
|
.addr_space = addr_space,
|
|
.access_mode = access_mode,
|
|
.binding = binding,
|
|
.group = group,
|
|
.init = init,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genOverride(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const extra = astgen.tree.extraData(Node.Override, astgen.tree.nodeLHS(node));
|
|
const name_loc = astgen.tree.declNameLoc(node).?;
|
|
|
|
var override_type = InstIndex.none;
|
|
if (extra.type != .none) {
|
|
override_type = try astgen.genType(scope, extra.type);
|
|
}
|
|
|
|
var id = InstIndex.none;
|
|
if (extra.attrs != .none) {
|
|
for (astgen.tree.spanToList(extra.attrs)) |attr| {
|
|
switch (astgen.tree.nodeTag(attr)) {
|
|
.attr_id => id = try astgen.attrId(scope, attr),
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"unexpected attribute '{s}'",
|
|
.{astgen.tree.nodeLoc(attr).slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
var init = InstIndex.none;
|
|
if (node_rhs != .none) {
|
|
init = try astgen.genExpr(scope, node_rhs);
|
|
}
|
|
|
|
const name = try astgen.addString(name_loc.slice(astgen.tree.source));
|
|
return astgen.addInst(.{
|
|
.@"var" = .{
|
|
.name = name,
|
|
.type = override_type,
|
|
.init = init,
|
|
.addr_space = .private,
|
|
.access_mode = .read,
|
|
.id = id,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genStruct(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const name_str = astgen.tree.declNameLoc(node).?.slice(astgen.tree.source);
|
|
const name = try astgen.addString(name_str);
|
|
const members = try astgen.genStructMembers(scope, astgen.tree.nodeLHS(node));
|
|
return astgen.addInst(.{
|
|
.@"struct" = .{
|
|
.name = name,
|
|
.members = members,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genStructMembers(astgen: *AstGen, scope: *Scope, node: NodeIndex) !RefIndex {
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
const member_nodes_list = astgen.tree.spanToList(node);
|
|
for (member_nodes_list, 0..) |member_node, i| {
|
|
const member_name_loc = astgen.tree.tokenLoc(astgen.tree.nodeToken(member_node));
|
|
const member_attrs_node = astgen.tree.nodeLHS(member_node);
|
|
const member_type_node = astgen.tree.nodeRHS(member_node);
|
|
const member_type_loc = astgen.tree.nodeLoc(member_type_node);
|
|
const member_type = astgen.genType(scope, member_type_node) catch |err| switch (err) {
|
|
error.AnalysisFail => continue,
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
const member_type_inst = astgen.getInst(member_type);
|
|
|
|
switch (member_type_inst) {
|
|
.array,
|
|
.atomic_type,
|
|
.@"struct",
|
|
=> {},
|
|
inline .bool, .int, .float, .vector, .matrix => |data| {
|
|
std.debug.assert(data.value == null);
|
|
},
|
|
else => {
|
|
try astgen.errors.add(
|
|
member_name_loc,
|
|
"invalid struct member type '{s}'",
|
|
.{member_type_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
if (member_type_inst == .array) {
|
|
const array_len = member_type_inst.array.len;
|
|
if (array_len == .none and i + 1 != member_nodes_list.len) {
|
|
try astgen.errors.add(
|
|
member_name_loc,
|
|
"struct member with runtime-sized array type, must be the last member of the structure",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
var @"align": ?u29 = null;
|
|
var size: ?u32 = null;
|
|
var builtin: ?Inst.Builtin = null;
|
|
var location: ?u16 = null;
|
|
var interpolate: ?Inst.Interpolate = null;
|
|
if (member_attrs_node != .none) {
|
|
for (astgen.tree.spanToList(member_attrs_node)) |attr| {
|
|
switch (astgen.tree.nodeTag(attr)) {
|
|
.attr_align => @"align" = try astgen.attrAlign(scope, attr),
|
|
.attr_size => size = try astgen.attrSize(scope, attr),
|
|
.attr_location => location = try astgen.attrLocation(scope, attr),
|
|
.attr_builtin => builtin = astgen.attrBuiltin(attr),
|
|
.attr_interpolate => interpolate = astgen.attrInterpolate(attr),
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"unexpected attribute '{s}'",
|
|
.{astgen.tree.nodeLoc(attr).slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
const name = try astgen.addString(member_name_loc.slice(astgen.tree.source));
|
|
const member = try astgen.addInst(.{
|
|
.struct_member = .{
|
|
.name = name,
|
|
.type = member_type,
|
|
.index = @intCast(i),
|
|
.@"align" = @"align",
|
|
.size = size,
|
|
.builtin = builtin,
|
|
.location = location,
|
|
.interpolate = interpolate,
|
|
},
|
|
});
|
|
try astgen.scratch.append(astgen.allocator, member);
|
|
}
|
|
|
|
return astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
}
|
|
|
|
fn genFn(astgen: *AstGen, root_scope: *Scope, node: NodeIndex, only_entry_point: bool) !InstIndex {
|
|
const scratch_top = astgen.global_var_refs.count();
|
|
defer astgen.global_var_refs.shrinkRetainingCapacity(scratch_top);
|
|
|
|
astgen.has_array_length = false;
|
|
|
|
const fn_proto = astgen.tree.extraData(Node.FnProto, astgen.tree.nodeLHS(node));
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
|
|
var return_type = InstIndex.none;
|
|
var return_attrs = Inst.Fn.ReturnAttrs{
|
|
.builtin = null,
|
|
.location = null,
|
|
.interpolate = null,
|
|
.invariant = false,
|
|
};
|
|
if (fn_proto.return_type != .none) {
|
|
return_type = try astgen.genType(root_scope, fn_proto.return_type);
|
|
|
|
if (fn_proto.return_attrs != .none) {
|
|
for (astgen.tree.spanToList(fn_proto.return_attrs)) |attr| {
|
|
switch (astgen.tree.nodeTag(attr)) {
|
|
.attr_invariant => return_attrs.invariant = true,
|
|
.attr_location => return_attrs.location = try astgen.attrLocation(root_scope, attr),
|
|
.attr_builtin => return_attrs.builtin = astgen.attrBuiltin(attr),
|
|
.attr_interpolate => return_attrs.interpolate = astgen.attrInterpolate(attr),
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"unexpected attribute '{s}'",
|
|
.{astgen.tree.nodeLoc(attr).slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var stage: Inst.Fn.Stage = .none;
|
|
var workgroup_size_attr = NodeIndex.none;
|
|
var is_const = false;
|
|
if (fn_proto.attrs != .none) {
|
|
for (astgen.tree.spanToList(fn_proto.attrs)) |attr| {
|
|
switch (astgen.tree.nodeTag(attr)) {
|
|
.attr_vertex,
|
|
.attr_fragment,
|
|
.attr_compute,
|
|
=> |stage_attr| {
|
|
if (stage != .none) {
|
|
try astgen.errors.add(astgen.tree.nodeLoc(attr), "multiple shader stages", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
stage = switch (stage_attr) {
|
|
.attr_vertex => .vertex,
|
|
.attr_fragment => .fragment,
|
|
.attr_compute => .{ .compute = undefined },
|
|
else => unreachable,
|
|
};
|
|
},
|
|
.attr_workgroup_size => workgroup_size_attr = attr,
|
|
.attr_const => is_const = true,
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"unexpected attribute '{s}'",
|
|
.{astgen.tree.nodeLoc(attr).slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
if (only_entry_point and stage == .none) return error.Skiped;
|
|
|
|
if (stage == .compute) {
|
|
if (return_type != .none) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(fn_proto.return_type),
|
|
"return type on compute function",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (workgroup_size_attr == .none) {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"@workgroup_size not specified on compute shader",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const workgroup_size_data = astgen.tree.extraData(Ast.Node.WorkgroupSize, astgen.tree.nodeLHS(workgroup_size_attr));
|
|
stage.compute = Inst.Fn.Stage.WorkgroupSize{
|
|
.x = try astgen.genExpr(root_scope, workgroup_size_data.x),
|
|
.y = blk: {
|
|
if (workgroup_size_data.y == .none) break :blk .none;
|
|
break :blk try astgen.genExpr(root_scope, workgroup_size_data.y);
|
|
},
|
|
.z = blk: {
|
|
if (workgroup_size_data.z == .none) break :blk .none;
|
|
break :blk try astgen.genExpr(root_scope, workgroup_size_data.z);
|
|
},
|
|
};
|
|
} else if (workgroup_size_attr != .none) {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"@workgroup_size must be specified with a compute shader",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const scope = try astgen.scope_pool.create();
|
|
scope.* = .{
|
|
.tag = .{
|
|
.@"fn" = .{
|
|
.stage = stage,
|
|
.return_type = return_type,
|
|
.returned = false,
|
|
.flattened_params = .{},
|
|
},
|
|
},
|
|
.parent = root_scope,
|
|
};
|
|
astgen.current_fn_scope = scope;
|
|
|
|
var params = RefIndex.none;
|
|
if (fn_proto.params != .none) {
|
|
params = try astgen.genFnParams(scope, fn_proto.params);
|
|
}
|
|
|
|
const name_slice = astgen.tree.declNameLoc(node).?.slice(astgen.tree.source);
|
|
const name = try astgen.addString(name_slice);
|
|
const block = try astgen.genBlock(scope, node_rhs);
|
|
|
|
if (return_type != .none and !scope.tag.@"fn".returned) {
|
|
try astgen.errors.add(node_loc, "function does not return", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const global_var_refs = try astgen.addRefList(astgen.global_var_refs.keys()[scratch_top..]);
|
|
|
|
const inst = try astgen.addInst(.{
|
|
.@"fn" = .{
|
|
.name = name,
|
|
.stage = stage,
|
|
.is_const = is_const,
|
|
.params = params,
|
|
.return_type = return_type,
|
|
.return_attrs = return_attrs,
|
|
.block = block,
|
|
.global_var_refs = global_var_refs,
|
|
.has_array_length = astgen.has_array_length,
|
|
},
|
|
});
|
|
|
|
if (astgen.entry_point_name) |entry_point_name| {
|
|
if (std.mem.eql(u8, name_slice, entry_point_name)) {
|
|
astgen.compute_stage = .none;
|
|
astgen.vertex_stage = .none;
|
|
astgen.fragment_stage = .none;
|
|
if (stage == .none) {
|
|
try astgen.errors.add(node_loc, "function is not an entry point", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
}
|
|
|
|
// only one kind of entry point per file
|
|
switch (stage) {
|
|
.none => {},
|
|
.compute => {
|
|
if (astgen.compute_stage != .none) {
|
|
try astgen.errors.add(node_loc, "multiple compute entry point found", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
astgen.compute_stage = inst;
|
|
},
|
|
.vertex => {
|
|
if (astgen.vertex_stage != .none) {
|
|
try astgen.errors.add(node_loc, "multiple vertex entry point found", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
astgen.vertex_stage = inst;
|
|
},
|
|
.fragment => {
|
|
if (astgen.fragment_stage != .none) {
|
|
try astgen.errors.add(node_loc, "multiple fragment entry point found", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
astgen.fragment_stage = inst;
|
|
},
|
|
}
|
|
|
|
return inst;
|
|
}
|
|
|
|
fn genTypeAlias(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
return astgen.genType(scope, node_lhs);
|
|
}
|
|
|
|
fn genFnParams(astgen: *AstGen, scope: *Scope, node: NodeIndex) !RefIndex {
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
const param_nodes = astgen.tree.spanToList(node);
|
|
try astgen.scanDecls(scope, param_nodes);
|
|
|
|
for (param_nodes) |param_node| {
|
|
const param_type_node = astgen.tree.nodeRHS(param_node);
|
|
const param_type = try astgen.genType(scope, param_type_node);
|
|
const param_name_loc = astgen.tree.tokenLoc(astgen.tree.nodeToken(param_node));
|
|
const param_name = try astgen.addString(param_name_loc.slice(astgen.tree.source));
|
|
|
|
if (scope.tag.@"fn".stage != .none and astgen.getInst(param_type) == .@"struct") {
|
|
const members = astgen.refToList(astgen.getInst(param_type).@"struct".members);
|
|
for (members) |member_inst| {
|
|
const member = astgen.getInst(member_inst).struct_member;
|
|
const param = try astgen.addInst(.{
|
|
.fn_param = .{
|
|
.name = member.name,
|
|
.type = member.type,
|
|
.builtin = member.builtin,
|
|
.interpolate = member.interpolate,
|
|
.location = member.location,
|
|
.invariant = false,
|
|
},
|
|
});
|
|
try astgen.current_fn_scope.tag.@"fn".flattened_params.put(
|
|
astgen.inst_arena.allocator(),
|
|
member_inst,
|
|
param,
|
|
);
|
|
try astgen.scratch.append(astgen.allocator, param);
|
|
}
|
|
|
|
// TODO
|
|
const param = try astgen.addInst(.{
|
|
.fn_param = .{
|
|
.name = param_name,
|
|
.type = param_type,
|
|
.builtin = null,
|
|
.interpolate = null,
|
|
.location = null,
|
|
.invariant = false,
|
|
},
|
|
});
|
|
scope.decls.putAssumeCapacity(param_node, param);
|
|
} else {
|
|
var builtin: ?Inst.Builtin = null;
|
|
var inter: ?Inst.Interpolate = null;
|
|
var location: ?u16 = null;
|
|
var invariant: bool = false;
|
|
|
|
const param_attrs_node = astgen.tree.nodeLHS(param_node);
|
|
if (param_attrs_node != .none) {
|
|
for (astgen.tree.spanToList(param_attrs_node)) |attr| {
|
|
switch (astgen.tree.nodeTag(attr)) {
|
|
.attr_invariant => invariant = true,
|
|
.attr_location => location = try astgen.attrLocation(scope, attr),
|
|
.attr_builtin => builtin = astgen.attrBuiltin(attr),
|
|
.attr_interpolate => inter = astgen.attrInterpolate(attr),
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(attr),
|
|
"unexpected attribute '{s}'",
|
|
.{astgen.tree.nodeLoc(attr).slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
const param = try astgen.addInst(.{
|
|
.fn_param = .{
|
|
.name = param_name,
|
|
.type = param_type,
|
|
.builtin = builtin,
|
|
.interpolate = inter,
|
|
.location = location,
|
|
.invariant = invariant,
|
|
},
|
|
});
|
|
try astgen.scratch.append(astgen.allocator, param);
|
|
scope.decls.putAssumeCapacity(param_node, param);
|
|
}
|
|
}
|
|
|
|
return astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
}
|
|
|
|
fn attrBinding(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const binding = try astgen.genExpr(scope, node_lhs);
|
|
|
|
const binding_res = try astgen.resolve(binding);
|
|
if (astgen.getInst(binding_res) != .int) {
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"binding value must be integer",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (astgen.getValue(Inst.Int.Value, astgen.getInst(binding_res).int.value.?).literal < 0) {
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"binding value must be a positive",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return binding;
|
|
}
|
|
|
|
fn attrId(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const id = try astgen.genExpr(scope, node_lhs);
|
|
|
|
const id_res = try astgen.resolve(id);
|
|
if (astgen.getInst(id_res) != .int) {
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"id value must be integer",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (astgen.getValue(Inst.Int.Value, astgen.getInst(id_res).int.value.?).literal < 0) {
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"id value must be a positive",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
fn attrGroup(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const group = try astgen.genExpr(scope, node_lhs);
|
|
|
|
const inst = astgen.getInst(try astgen.resolve(group));
|
|
if (inst != .int or inst.int.value == null) {
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"group value must be a constant integer",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (astgen.getValue(Inst.Int.Value, inst.int.value.?).literal < 0) {
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"group value must be a positive",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return group;
|
|
}
|
|
|
|
fn attrAlign(astgen: *AstGen, scope: *Scope, node: NodeIndex) !u29 {
|
|
const expr = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
return @intCast(astgen.getValue(Air.Inst.Int.Value, astgen.getInst(expr).int.value.?).literal);
|
|
}
|
|
|
|
fn attrSize(astgen: *AstGen, scope: *Scope, node: NodeIndex) !u32 {
|
|
const expr = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
return @intCast(astgen.getValue(Air.Inst.Int.Value, astgen.getInst(expr).int.value.?).literal);
|
|
}
|
|
|
|
fn attrLocation(astgen: *AstGen, scope: *Scope, node: NodeIndex) !u16 {
|
|
const inst_idx = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
const value_idx = astgen.getInst(inst_idx).int.value.?;
|
|
return @intCast(astgen.getValue(Inst.Int.Value, value_idx).literal);
|
|
}
|
|
|
|
fn attrBuiltin(astgen: *AstGen, node: NodeIndex) Inst.Builtin {
|
|
const loc = astgen.tree.tokenLoc(astgen.tree.nodeLHS(node).asTokenIndex());
|
|
return stringToEnum(Ast.Builtin, loc.slice(astgen.tree.source)).?;
|
|
}
|
|
|
|
fn attrInterpolate(astgen: *AstGen, node: NodeIndex) Inst.Interpolate {
|
|
const inter_type_token = astgen.tree.nodeLHS(node).asTokenIndex();
|
|
const inter_type_loc = astgen.tree.tokenLoc(inter_type_token);
|
|
const inter_type_ast = stringToEnum(Ast.InterpolationType, inter_type_loc.slice(astgen.tree.source)).?;
|
|
|
|
var inter = Inst.Interpolate{
|
|
.type = switch (inter_type_ast) {
|
|
.perspective => .perspective,
|
|
.linear => .linear,
|
|
.flat => .flat,
|
|
},
|
|
.sample = .none,
|
|
};
|
|
|
|
if (astgen.tree.nodeRHS(node) != .none) {
|
|
const inter_sample_token = astgen.tree.nodeRHS(node).asTokenIndex();
|
|
const inter_sample_loc = astgen.tree.tokenLoc(inter_sample_token);
|
|
const inter_sample_ast = stringToEnum(Ast.InterpolationSample, inter_sample_loc.slice(astgen.tree.source)).?;
|
|
inter.sample = switch (inter_sample_ast) {
|
|
.center => .center,
|
|
.centroid => .centroid,
|
|
.sample => .sample,
|
|
};
|
|
}
|
|
|
|
return inter;
|
|
}
|
|
|
|
fn genBlock(astgen: *AstGen, scope: *Scope, node: NodeIndex) error{ OutOfMemory, AnalysisFail }!InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) return .none;
|
|
|
|
const stmnt_nodes = astgen.tree.spanToList(node_lhs);
|
|
try astgen.scanDecls(scope, stmnt_nodes);
|
|
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
var is_unreachable = false;
|
|
for (stmnt_nodes) |stmnt_node| {
|
|
const stmnt_node_loc = astgen.tree.nodeLoc(stmnt_node);
|
|
if (is_unreachable) {
|
|
try astgen.errors.add(stmnt_node_loc, "unreachable code", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
const stmnt = try astgen.genStatement(scope, stmnt_node);
|
|
if (astgen.getInst(stmnt) == .@"return") {
|
|
is_unreachable = true;
|
|
}
|
|
try astgen.scratch.append(astgen.allocator, stmnt);
|
|
}
|
|
|
|
const statements = try astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
return astgen.addInst(.{ .block = statements });
|
|
}
|
|
|
|
fn genStatement(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
return switch (astgen.tree.nodeTag(node)) {
|
|
.compound_assign => try astgen.genCompoundAssign(scope, node),
|
|
.phony_assign => try astgen.genPhonyAssign(scope, node),
|
|
.call => try astgen.genCall(scope, node),
|
|
.@"return" => try astgen.genReturn(scope, node),
|
|
.break_if => try astgen.genBreakIf(scope, node),
|
|
.@"if" => try astgen.genIf(scope, node),
|
|
.if_else => try astgen.genIfElse(scope, node),
|
|
.if_else_if => try astgen.genIfElseIf(scope, node),
|
|
.@"while" => try astgen.genWhile(scope, node),
|
|
.@"for" => try astgen.genFor(scope, node),
|
|
.@"switch" => try astgen.genSwitch(scope, node),
|
|
.loop => try astgen.genLoop(scope, node),
|
|
.block => blk: {
|
|
const inner_scope = try astgen.scope_pool.create();
|
|
inner_scope.* = .{ .tag = .block, .parent = scope };
|
|
const inner_block = try astgen.genBlock(inner_scope, node);
|
|
break :blk inner_block;
|
|
},
|
|
.continuing => try astgen.genContinuing(scope, node),
|
|
.discard => try astgen.addInst(.discard),
|
|
.@"break" => try astgen.addInst(.@"break"),
|
|
.@"continue" => try astgen.addInst(.@"continue"),
|
|
.increase => try astgen.genIncreaseDecrease(scope, node, .add),
|
|
.decrease => try astgen.genIncreaseDecrease(scope, node, .sub),
|
|
.@"var" => blk: {
|
|
const decl = try astgen.genVar(scope, node);
|
|
scope.decls.putAssumeCapacity(node, decl);
|
|
break :blk decl;
|
|
},
|
|
.@"const" => blk: {
|
|
const decl = try astgen.genConst(scope, node);
|
|
scope.decls.putAssumeCapacity(node, decl);
|
|
break :blk decl;
|
|
},
|
|
.let => blk: {
|
|
const decl = try astgen.genLet(scope, node);
|
|
scope.decls.putAssumeCapacity(node, decl);
|
|
break :blk decl;
|
|
},
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn genLoop(astgen: *AstGen, parent_scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const scope = try astgen.scope_pool.create();
|
|
scope.* = .{ .tag = .loop, .parent = parent_scope };
|
|
|
|
const block = try astgen.genBlock(scope, astgen.tree.nodeLHS(node));
|
|
return astgen.addInst(.{ .loop = block });
|
|
}
|
|
|
|
fn genContinuing(astgen: *AstGen, parent_scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const scope = try astgen.scope_pool.create();
|
|
scope.* = .{ .tag = .continuing, .parent = parent_scope };
|
|
|
|
const block = try astgen.genBlock(scope, astgen.tree.nodeLHS(node));
|
|
return astgen.addInst(.{ .continuing = block });
|
|
}
|
|
|
|
fn genBreakIf(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const expr = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
return astgen.addInst(.{ .break_if = expr });
|
|
}
|
|
|
|
fn genIf(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
|
|
const cond = try astgen.genExpr(scope, node_lhs);
|
|
const cond_res = try astgen.resolve(cond);
|
|
if (astgen.getInst(cond_res) != .bool) {
|
|
try astgen.errors.add(node_lhs_loc, "expected bool", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const body_scope = try astgen.scope_pool.create();
|
|
body_scope.* = .{ .tag = .@"if", .parent = scope };
|
|
const block = try astgen.genBlock(body_scope, node_rhs);
|
|
|
|
return astgen.addInst(.{
|
|
.@"if" = .{
|
|
.cond = cond,
|
|
.body = block,
|
|
.@"else" = .none,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genIfElse(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const if_node = astgen.tree.nodeLHS(node);
|
|
const cond = try astgen.genExpr(scope, astgen.tree.nodeLHS(if_node));
|
|
|
|
const if_body_scope = try astgen.scope_pool.create();
|
|
if_body_scope.* = .{ .tag = .@"if", .parent = scope };
|
|
const if_block = try astgen.genBlock(if_body_scope, astgen.tree.nodeRHS(if_node));
|
|
|
|
const else_body_scope = try astgen.scope_pool.create();
|
|
else_body_scope.* = .{ .tag = .@"if", .parent = scope };
|
|
const else_block = try astgen.genBlock(else_body_scope, astgen.tree.nodeRHS(node));
|
|
|
|
return astgen.addInst(.{
|
|
.@"if" = .{
|
|
.cond = cond,
|
|
.body = if_block,
|
|
.@"else" = else_block,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genIfElseIf(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const if_node = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const cond = try astgen.genExpr(scope, astgen.tree.nodeLHS(if_node));
|
|
const block = try astgen.genBlock(scope, astgen.tree.nodeRHS(if_node));
|
|
const else_if = switch (astgen.tree.nodeTag(node_rhs)) {
|
|
.@"if" => try astgen.genIf(scope, node_rhs),
|
|
.if_else => try astgen.genIfElse(scope, node_rhs),
|
|
.if_else_if => try astgen.genIfElseIf(scope, node_rhs),
|
|
else => unreachable,
|
|
};
|
|
return astgen.addInst(.{
|
|
.@"if" = .{
|
|
.cond = cond,
|
|
.body = block,
|
|
.@"else" = else_if,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genWhile(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
|
|
const cond = try astgen.genExpr(scope, node_lhs);
|
|
const cond_res = try astgen.resolve(cond);
|
|
if (astgen.getInst(cond_res) != .bool) {
|
|
try astgen.errors.add(node_lhs_loc, "expected bool", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const block = try astgen.genBlock(scope, node_rhs);
|
|
return astgen.addInst(.{ .@"while" = .{ .cond = cond, .body = block } });
|
|
}
|
|
|
|
fn genFor(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const extra = astgen.tree.extraData(Ast.Node.ForHeader, node_lhs);
|
|
|
|
var for_scope = try astgen.scope_pool.create();
|
|
for_scope.* = .{ .tag = .@"for", .parent = scope };
|
|
|
|
try astgen.scanDecls(for_scope, &.{extra.init});
|
|
const init = switch (astgen.tree.nodeTag(extra.init)) {
|
|
.@"var" => try astgen.genVar(for_scope, extra.init),
|
|
.@"const" => try astgen.genConst(for_scope, extra.init),
|
|
.let => try astgen.genLet(for_scope, extra.init),
|
|
else => unreachable,
|
|
};
|
|
for_scope.decls.putAssumeCapacity(extra.init, init);
|
|
|
|
const cond_node_loc = astgen.tree.nodeLoc(extra.cond);
|
|
const cond = try astgen.genExpr(for_scope, extra.cond);
|
|
const cond_res = try astgen.resolve(cond);
|
|
if (astgen.getInst(cond_res) != .bool) {
|
|
try astgen.errors.add(cond_node_loc, "expected bool", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const update = switch (astgen.tree.nodeTag(extra.update)) {
|
|
.phony_assign => try astgen.genPhonyAssign(for_scope, extra.update),
|
|
.increase => try astgen.genIncreaseDecrease(for_scope, extra.update, .add),
|
|
.decrease => try astgen.genIncreaseDecrease(for_scope, extra.update, .sub),
|
|
.compound_assign => try astgen.genCompoundAssign(for_scope, extra.update),
|
|
.call => try astgen.genCall(for_scope, extra.update),
|
|
else => unreachable,
|
|
};
|
|
|
|
const block = try astgen.genBlock(for_scope, node_rhs);
|
|
|
|
return astgen.addInst(.{
|
|
.@"for" = .{
|
|
.init = init,
|
|
.cond = cond,
|
|
.update = update,
|
|
.body = block,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genSwitch(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const switch_on = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
const switch_on_res = try astgen.resolve(switch_on);
|
|
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
const cases_nodes = astgen.tree.spanToList(astgen.tree.nodeRHS(node));
|
|
for (cases_nodes) |cases_node| {
|
|
const cases_node_tag = astgen.tree.nodeTag(cases_node);
|
|
|
|
const cases_scope = try astgen.scope_pool.create();
|
|
cases_scope.* = .{ .tag = .switch_case, .parent = scope };
|
|
|
|
var cases = RefIndex.none;
|
|
const body = try astgen.genBlock(cases_scope, astgen.tree.nodeRHS(cases_node));
|
|
const default = cases_node_tag == .switch_default or cases_node_tag == .switch_case_default;
|
|
|
|
switch (cases_node_tag) {
|
|
.switch_case, .switch_case_default => {
|
|
const cases_scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(cases_scratch_top);
|
|
|
|
const case_nodes = astgen.tree.spanToList(astgen.tree.nodeLHS(cases_node));
|
|
for (case_nodes) |case_node| {
|
|
const case_node_loc = astgen.tree.nodeLoc(case_node);
|
|
const case = try astgen.genExpr(scope, case_node);
|
|
const case_res = try astgen.resolve(case);
|
|
if (!try astgen.coerce(case_res, switch_on_res)) {
|
|
try astgen.errors.add(case_node_loc, "switch and case type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
try astgen.scratch.append(astgen.allocator, case);
|
|
}
|
|
|
|
cases = try astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
},
|
|
.switch_default => {},
|
|
else => unreachable,
|
|
}
|
|
|
|
const case_inst = try astgen.addInst(.{
|
|
.switch_case = .{
|
|
.cases = cases,
|
|
.body = body,
|
|
.default = default,
|
|
},
|
|
});
|
|
try astgen.scratch.append(astgen.allocator, case_inst);
|
|
}
|
|
|
|
const cases_list = try astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
return astgen.addInst(.{
|
|
.@"switch" = .{
|
|
.switch_on = switch_on,
|
|
.cases_list = cases_list,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genCompoundAssign(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const lhs = try astgen.genExpr(scope, node_lhs);
|
|
const rhs = try astgen.genExpr(scope, node_rhs);
|
|
const lhs_res = try astgen.resolve(lhs);
|
|
const rhs_res = try astgen.resolve(rhs);
|
|
|
|
if (!try astgen.isMutable(lhs)) {
|
|
try astgen.errors.add(astgen.tree.nodeLoc(node), "cannot assign to constant", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (!try astgen.coerce(rhs_res, lhs_res)) {
|
|
try astgen.errors.add(astgen.tree.nodeLoc(node), "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const mod: Inst.Assign.Modifier = switch (astgen.tree.tokenTag(astgen.tree.nodeToken(node))) {
|
|
.equal => .none,
|
|
.plus_equal => .add,
|
|
.minus_equal => .sub,
|
|
.asterisk_equal => .mul,
|
|
.slash_equal => .div,
|
|
.percent_equal => .mod,
|
|
.ampersand_equal => .@"and",
|
|
.pipe_equal => .@"or",
|
|
.xor_equal => .xor,
|
|
.angle_bracket_angle_bracket_left_equal => .shl,
|
|
.angle_bracket_angle_bracket_right_equal => .shr,
|
|
else => unreachable,
|
|
};
|
|
|
|
return astgen.addInst(.{
|
|
.assign = .{
|
|
.mod = mod,
|
|
.type = lhs_res,
|
|
.lhs = lhs,
|
|
.rhs = rhs,
|
|
},
|
|
});
|
|
}
|
|
|
|
pub fn isMutable(astgen: *AstGen, index: InstIndex) !bool {
|
|
var idx = index;
|
|
while (true) switch (astgen.getInst(idx)) {
|
|
inline .field_access, .swizzle_access, .index_access => |access| idx = access.base,
|
|
.unary => |un| switch (un.op) {
|
|
.deref => return astgen.getInst(try astgen.resolve(un.expr)).ptr_type.access_mode != .read,
|
|
else => unreachable,
|
|
},
|
|
.var_ref => |var_ref| idx = var_ref,
|
|
.@"var" => |@"var"| return @"var".access_mode != .read,
|
|
else => return false,
|
|
};
|
|
}
|
|
|
|
fn genPhonyAssign(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
return astgen.genExpr(scope, node_lhs);
|
|
}
|
|
|
|
fn genIncreaseDecrease(astgen: *AstGen, scope: *Scope, node: NodeIndex, mod: Inst.Assign.Modifier) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
|
|
const lhs = try astgen.genExpr(scope, node_lhs);
|
|
if (astgen.getInst(lhs) != .var_ref) {
|
|
try astgen.errors.add(node_lhs_loc, "expected a reference", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const lhs_res = try astgen.resolve(lhs);
|
|
if (astgen.getInst(lhs_res) != .int) {
|
|
try astgen.errors.add(node_lhs_loc, "expected an integer", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const rhs = try astgen.addInst(.{ .int = .{
|
|
.type = astgen.getInst(lhs_res).int.type,
|
|
.value = try astgen.addValue(Inst.Int.Value, .{ .literal = 1 }),
|
|
} });
|
|
|
|
return astgen.addInst(.{ .assign = .{
|
|
.mod = mod,
|
|
.type = lhs_res,
|
|
.lhs = lhs,
|
|
.rhs = rhs,
|
|
} });
|
|
}
|
|
|
|
fn genVar(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const extra = astgen.tree.extraData(Node.Var, astgen.tree.nodeLHS(node));
|
|
const name_loc = astgen.tree.declNameLoc(node).?;
|
|
|
|
var is_resource = false; // TODO: research and remove this?
|
|
var addr_space = Inst.PointerType.AddressSpace.function;
|
|
if (extra.addr_space != .none) {
|
|
const addr_space_loc = astgen.tree.tokenLoc(extra.addr_space);
|
|
const ast_addr_space = stringToEnum(Ast.AddressSpace, addr_space_loc.slice(astgen.tree.source)).?;
|
|
addr_space = switch (ast_addr_space) {
|
|
.function => .function,
|
|
.private => .private,
|
|
.workgroup => .workgroup,
|
|
.uniform => .uniform,
|
|
.storage => .storage,
|
|
};
|
|
}
|
|
|
|
if (addr_space == .uniform or addr_space == .storage) {
|
|
is_resource = true;
|
|
}
|
|
|
|
var access_mode = Inst.PointerType.AccessMode.read_write;
|
|
if (extra.access_mode != .none) {
|
|
const access_mode_loc = astgen.tree.tokenLoc(extra.access_mode);
|
|
const ast_access_mode = stringToEnum(Ast.AccessMode, access_mode_loc.slice(astgen.tree.source)).?;
|
|
access_mode = switch (ast_access_mode) {
|
|
.read => .read,
|
|
.write => .write,
|
|
.read_write => .read_write,
|
|
};
|
|
}
|
|
|
|
var init = InstIndex.none;
|
|
if (node_rhs != .none) {
|
|
init = try astgen.genExpr(scope, node_rhs);
|
|
}
|
|
|
|
var var_type = InstIndex.none;
|
|
if (extra.type != .none) {
|
|
var_type = try astgen.genType(scope, extra.type);
|
|
|
|
switch (astgen.getInst(var_type)) {
|
|
.sampler_type,
|
|
.comparison_sampler_type,
|
|
.texture_type,
|
|
.external_texture_type,
|
|
=> {
|
|
is_resource = true;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (init != .none) {
|
|
const init_res = try astgen.resolve(init);
|
|
if (!try astgen.coerce(init_res, var_type)) {
|
|
try astgen.errors.add(astgen.tree.nodeLoc(node_rhs), "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
} else {
|
|
var_type = try astgen.resolve(init);
|
|
}
|
|
|
|
const name = try astgen.addString(name_loc.slice(astgen.tree.source));
|
|
return astgen.addInst(.{
|
|
.@"var" = .{
|
|
.name = name,
|
|
.type = var_type,
|
|
.addr_space = addr_space,
|
|
.access_mode = access_mode,
|
|
.init = init,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genConst(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const name_loc = astgen.tree.declNameLoc(node).?;
|
|
const init = try astgen.genExpr(scope, node_rhs);
|
|
var var_type = InstIndex.none;
|
|
if (node_lhs != .none) {
|
|
var_type = try astgen.genType(scope, node_lhs);
|
|
} else {
|
|
var_type = try astgen.resolve(init);
|
|
}
|
|
const name = try astgen.addString(name_loc.slice(astgen.tree.source));
|
|
return astgen.addInst(.{
|
|
.@"const" = .{
|
|
.name = name,
|
|
.type = var_type,
|
|
.init = init,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genLet(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const name_loc = astgen.tree.declNameLoc(node).?;
|
|
|
|
const init = try astgen.genExpr(scope, node_rhs);
|
|
const name = try astgen.addString(name_loc.slice(astgen.tree.source));
|
|
|
|
var var_type = InstIndex.none;
|
|
if (node_lhs != .none) {
|
|
var_type = try astgen.genType(scope, node_lhs);
|
|
} else {
|
|
var_type = try astgen.resolve(init);
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.@"var" = .{
|
|
.name = name,
|
|
.type = var_type,
|
|
.init = init,
|
|
.addr_space = .function,
|
|
.access_mode = .read,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genExpr(astgen: *AstGen, scope: *Scope, node: NodeIndex) error{ OutOfMemory, AnalysisFail }!InstIndex {
|
|
const node_tag = astgen.tree.nodeTag(node);
|
|
return switch (node_tag) {
|
|
.number => astgen.genNumber(node),
|
|
.true => astgen.addInst(.{ .bool = .{ .value = .{ .literal = true } } }),
|
|
.false => astgen.addInst(.{ .bool = .{ .value = .{ .literal = false } } }),
|
|
.not => astgen.genNot(scope, node),
|
|
.negate => astgen.genNegate(scope, node),
|
|
.deref => astgen.genDeref(scope, node),
|
|
.addr_of => astgen.genAddrOf(scope, node),
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
.add,
|
|
.sub,
|
|
.shl,
|
|
.shr,
|
|
.@"and",
|
|
.@"or",
|
|
.xor,
|
|
.logical_and,
|
|
.logical_or,
|
|
.equal,
|
|
.not_equal,
|
|
.less_than,
|
|
.less_than_equal,
|
|
.greater_than,
|
|
.greater_than_equal,
|
|
=> astgen.genBinary(scope, node),
|
|
.index_access => astgen.genIndexAccess(scope, node),
|
|
.field_access => astgen.genFieldAccess(scope, node),
|
|
.call => astgen.genCall(scope, node),
|
|
.bitcast => astgen.genBitcast(scope, node),
|
|
.ident => astgen.genVarRef(scope, node),
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn genNumber(astgen: *AstGen, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const bytes = node_loc.slice(astgen.tree.source);
|
|
|
|
var i: usize = 0;
|
|
var suffix: u8 = 0;
|
|
var base: u8 = 10;
|
|
var exponent = false;
|
|
var dot = false;
|
|
|
|
if (bytes.len >= 2 and bytes[0] == '0') switch (bytes[1]) {
|
|
'0'...'9' => {
|
|
try astgen.errors.add(node_loc, "leading zero disallowed", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
'x', 'X' => {
|
|
i = 2;
|
|
base = 16;
|
|
},
|
|
else => {},
|
|
};
|
|
|
|
while (i < bytes.len) : (i += 1) {
|
|
const c = bytes[i];
|
|
switch (c) {
|
|
'f', 'h' => suffix = c,
|
|
'i', 'u' => {
|
|
if (dot or suffix == 'f' or suffix == 'h' or exponent) {
|
|
try astgen.errors.add(node_loc, "suffix '{c}' on float literal", .{c}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
suffix = c;
|
|
},
|
|
'e', 'E', 'p', 'P' => {
|
|
if (exponent) {
|
|
try astgen.errors.add(node_loc, "duplicate exponent '{c}'", .{c}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
exponent = true;
|
|
},
|
|
'.' => dot = true,
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
var inst: Inst = undefined;
|
|
if (dot or exponent or suffix == 'f' or suffix == 'h') {
|
|
if (base == 16) {
|
|
// TODO
|
|
try astgen.errors.add(node_loc, "hexadecimal float literals not implemented", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const value = std.fmt.parseFloat(f32, bytes[0 .. bytes.len - @intFromBool(suffix != 0)]) catch |err| {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"cannot parse float literal ({s})",
|
|
.{@errorName(err)},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"this is a bug in sysgpu. please report it",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
};
|
|
|
|
inst = .{
|
|
.float = .{
|
|
.type = switch (suffix) {
|
|
0, 'f' => .f32,
|
|
'h' => .f16,
|
|
else => unreachable,
|
|
},
|
|
.value = try astgen.addValue(Inst.Float.Value, .{ .literal = value }),
|
|
},
|
|
};
|
|
} else {
|
|
const value = std.fmt.parseInt(i33, bytes[0 .. bytes.len - @intFromBool(suffix != 0)], 0) catch |err| {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"cannot parse integer literal ({s})",
|
|
.{@errorName(err)},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"this is a bug in sysgpu. please report it",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
};
|
|
|
|
inst = .{
|
|
.int = .{
|
|
.type = switch (suffix) {
|
|
0, 'i' => .i32,
|
|
'u' => .u32,
|
|
else => unreachable,
|
|
},
|
|
.value = try astgen.addValue(Inst.Int.Value, .{ .literal = value }),
|
|
},
|
|
};
|
|
}
|
|
return astgen.addInst(inst);
|
|
}
|
|
|
|
fn coerce(astgen: *AstGen, src: InstIndex, dst: InstIndex) !bool {
|
|
if (astgen.eql(src, dst)) return true;
|
|
|
|
const src_inst = astgen.getInst(src);
|
|
const dst_inst = astgen.getInst(dst);
|
|
|
|
switch (src_inst) {
|
|
.int => |src_int| if (src_int.value != null) {
|
|
const int_value = astgen.getValue(Air.Inst.Int.Value, src_inst.int.value.?);
|
|
if (int_value == .literal) switch (dst_inst) {
|
|
.int => |dst_int| {
|
|
if (src_int.type == .i32 and dst_int.type == .u32 and int_value.literal < 0) {
|
|
try astgen.errors.add(
|
|
Loc{ .start = 0, .end = 0 },
|
|
"TODO: undefined behavior: Op ({d}, rhs)",
|
|
.{int_value.literal},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const value = try astgen.addValue(
|
|
Air.Inst.Int.Value,
|
|
Air.Inst.Int.Value{ .literal = @intCast(int_value.literal) },
|
|
);
|
|
astgen.instructions.keys()[@intFromEnum(src)] = .{
|
|
.int = .{
|
|
.type = dst_int.type,
|
|
.value = value,
|
|
},
|
|
};
|
|
return true;
|
|
},
|
|
.float => |dst_float| {
|
|
const value = try astgen.addValue(
|
|
Air.Inst.Float.Value,
|
|
Air.Inst.Float.Value{ .literal = @floatFromInt(int_value.literal) },
|
|
);
|
|
astgen.instructions.keys()[@intFromEnum(src)] = .{
|
|
.float = .{
|
|
.type = dst_float.type,
|
|
.value = value,
|
|
},
|
|
};
|
|
return true;
|
|
},
|
|
else => {},
|
|
};
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
fn genNot(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const expr = try astgen.genExpr(scope, node_lhs);
|
|
|
|
const expr_res = try astgen.resolve(expr);
|
|
if (astgen.getInst(expr_res) == .bool) {
|
|
return astgen.addInst(.{ .unary = .{ .op = .not, .result_type = expr_res, .expr = expr } });
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"cannot operate not (!) on '{s}'",
|
|
.{node_lhs_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genNegate(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const expr = try astgen.genExpr(scope, node_lhs);
|
|
|
|
const expr_res = try astgen.resolve(expr);
|
|
switch (astgen.getInst(expr_res)) {
|
|
.int, .float => return astgen.addInst(.{
|
|
.unary = .{
|
|
.op = .negate,
|
|
.result_type = expr_res,
|
|
.expr = expr,
|
|
},
|
|
}),
|
|
else => {},
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"cannot negate '{s}'",
|
|
.{node_lhs_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genDeref(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const expr = try astgen.genExpr(scope, node_lhs);
|
|
const expr_res = try astgen.resolve(expr);
|
|
const expr_res_inst = astgen.getInst(expr_res);
|
|
|
|
if (expr_res_inst == .ptr_type) {
|
|
return astgen.addInst(.{
|
|
.unary = .{
|
|
.op = .deref,
|
|
.result_type = expr_res_inst.ptr_type.elem_type,
|
|
.expr = expr,
|
|
},
|
|
});
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
node_lhs_loc,
|
|
"cannot dereference '{s}'",
|
|
.{node_lhs_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genAddrOf(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const expr = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
const expr_res = try astgen.resolve(expr);
|
|
const result_type = try astgen.addInst(.{
|
|
.ptr_type = .{
|
|
.elem_type = expr_res,
|
|
.addr_space = .function, // TODO
|
|
.access_mode = .read_write, // TODO
|
|
},
|
|
});
|
|
|
|
const inst = try astgen.addInst(.{
|
|
.unary = .{
|
|
.op = .addr_of,
|
|
.result_type = result_type,
|
|
.expr = expr,
|
|
},
|
|
});
|
|
return inst;
|
|
}
|
|
|
|
fn genBinary(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_tag = astgen.tree.nodeTag(node);
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const lhs = try astgen.genExpr(scope, node_lhs);
|
|
const rhs = try astgen.genExpr(scope, node_rhs);
|
|
|
|
const lhs_res = try astgen.resolve(lhs);
|
|
const rhs_res = try astgen.resolve(rhs);
|
|
const lhs_res_inst = astgen.getInst(lhs_res);
|
|
const rhs_res_inst = astgen.getInst(rhs_res);
|
|
|
|
var is_valid = false;
|
|
var vector_size: ?Inst.Vector.Size = null;
|
|
var arithmetic_res_type = InstIndex.none;
|
|
|
|
switch (node_tag) {
|
|
.shl, .shr, .@"and", .@"or", .xor => {
|
|
is_valid = lhs_res_inst == .int and rhs_res_inst == .int;
|
|
},
|
|
.logical_and, .logical_or => {
|
|
is_valid = lhs_res_inst == .bool and rhs_res_inst == .bool;
|
|
},
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
.add,
|
|
.sub,
|
|
.equal,
|
|
.not_equal,
|
|
.less_than,
|
|
.less_than_equal,
|
|
.greater_than,
|
|
.greater_than_equal,
|
|
=> switch (lhs_res_inst) {
|
|
.int, .float => {
|
|
if (try astgen.coerce(rhs_res, lhs_res) or try astgen.coerce(lhs_res, rhs_res)) {
|
|
is_valid = true;
|
|
arithmetic_res_type = lhs_res;
|
|
}
|
|
|
|
switch (rhs_res_inst) {
|
|
.vector => |v| {
|
|
if (try astgen.coerce(lhs_res, rhs_res_inst.vector.elem_type)) {
|
|
is_valid = true;
|
|
vector_size = v.size;
|
|
arithmetic_res_type = rhs_res;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
.vector => |v| {
|
|
vector_size = v.size;
|
|
if (astgen.eql(rhs_res, lhs_res)) {
|
|
is_valid = true;
|
|
arithmetic_res_type = lhs_res;
|
|
}
|
|
|
|
if (try astgen.coerce(rhs_res, lhs_res_inst.vector.elem_type)) {
|
|
is_valid = true;
|
|
arithmetic_res_type = lhs_res;
|
|
}
|
|
|
|
if (rhs_res_inst == .matrix) {
|
|
if (astgen.getInst(lhs_res_inst.vector.elem_type) == .float) {
|
|
if (lhs_res_inst.vector.size == rhs_res_inst.matrix.cols) {
|
|
is_valid = true;
|
|
arithmetic_res_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = lhs_res_inst.vector.elem_type,
|
|
.size = rhs_res_inst.matrix.rows,
|
|
.value = null,
|
|
} });
|
|
}
|
|
|
|
if (lhs_res_inst.vector.size == rhs_res_inst.matrix.rows) {
|
|
is_valid = true;
|
|
arithmetic_res_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = lhs_res_inst.vector.elem_type,
|
|
.size = rhs_res_inst.matrix.cols,
|
|
.value = null,
|
|
} });
|
|
}
|
|
}
|
|
}
|
|
},
|
|
.matrix => {
|
|
if (rhs_res_inst == .matrix) {
|
|
if (astgen.eql(lhs_res_inst.matrix.elem_type, rhs_res_inst.matrix.elem_type)) {
|
|
// matCxR<T> matCxR<T>
|
|
if (lhs_res_inst.matrix.rows == rhs_res_inst.matrix.rows and
|
|
lhs_res_inst.matrix.cols == rhs_res_inst.matrix.cols)
|
|
{
|
|
is_valid = true;
|
|
arithmetic_res_type = lhs_res;
|
|
}
|
|
|
|
// matKxR<T> matCxK<T>
|
|
if (lhs_res_inst.matrix.cols == rhs_res_inst.matrix.rows) {
|
|
is_valid = true;
|
|
arithmetic_res_type = try astgen.addInst(.{ .matrix = .{
|
|
.elem_type = lhs_res_inst.matrix.elem_type,
|
|
.cols = rhs_res_inst.matrix.cols,
|
|
.rows = lhs_res_inst.matrix.rows,
|
|
.value = null,
|
|
} });
|
|
}
|
|
|
|
// matCxK<T> matKxR<T>
|
|
if (rhs_res_inst.matrix.cols == lhs_res_inst.matrix.rows) {
|
|
is_valid = true;
|
|
arithmetic_res_type = try astgen.addInst(.{ .matrix = .{
|
|
.elem_type = lhs_res_inst.matrix.elem_type,
|
|
.cols = lhs_res_inst.matrix.cols,
|
|
.rows = rhs_res_inst.matrix.rows,
|
|
.value = null,
|
|
} });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rhs_res_inst == .float) {
|
|
is_valid = true;
|
|
arithmetic_res_type = lhs_res;
|
|
}
|
|
|
|
if (rhs_res_inst == .vector) {
|
|
if (rhs_res_inst.vector.size == lhs_res_inst.matrix.cols) {
|
|
is_valid = true;
|
|
arithmetic_res_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = rhs_res_inst.vector.elem_type,
|
|
.size = lhs_res_inst.matrix.rows,
|
|
.value = null,
|
|
} });
|
|
}
|
|
|
|
if (rhs_res_inst.vector.size == lhs_res_inst.matrix.rows) {
|
|
is_valid = true;
|
|
arithmetic_res_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = rhs_res_inst.vector.elem_type,
|
|
.size = lhs_res_inst.matrix.cols,
|
|
.value = null,
|
|
} });
|
|
}
|
|
}
|
|
},
|
|
else => {},
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
if (!is_valid) {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"invalid operation between {s} and {s}",
|
|
.{ @tagName(lhs_res_inst), @tagName(rhs_res_inst) },
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const op: Inst.Binary.Op = switch (node_tag) {
|
|
.mul => .mul,
|
|
.div => .div,
|
|
.mod => .mod,
|
|
.add => .add,
|
|
.sub => .sub,
|
|
.shl => .shl,
|
|
.shr => .shr,
|
|
.@"and" => .@"and",
|
|
.@"or" => .@"or",
|
|
.xor => .xor,
|
|
.logical_and => .logical_and,
|
|
.logical_or => .logical_or,
|
|
.equal => .equal,
|
|
.not_equal => .not_equal,
|
|
.less_than => .less_than,
|
|
.less_than_equal => .less_than_equal,
|
|
.greater_than => .greater_than,
|
|
.greater_than_equal => .greater_than_equal,
|
|
else => unreachable,
|
|
};
|
|
|
|
const res_type = switch (op) {
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
.add,
|
|
.sub,
|
|
=> arithmetic_res_type,
|
|
.logical_and,
|
|
.logical_or,
|
|
.equal,
|
|
.not_equal,
|
|
.less_than,
|
|
.less_than_equal,
|
|
.greater_than,
|
|
.greater_than_equal,
|
|
=> if (vector_size) |size|
|
|
try astgen.addInst(.{ .vector = .{
|
|
.elem_type = try astgen.addInst(.{ .bool = .{ .value = null } }),
|
|
.size = size,
|
|
.value = null,
|
|
} })
|
|
else
|
|
try astgen.addInst(.{ .bool = .{ .value = null } }),
|
|
else => lhs_res,
|
|
};
|
|
|
|
return astgen.addInst(.{ .binary = .{
|
|
.op = op,
|
|
.result_type = res_type,
|
|
.lhs_type = lhs_res,
|
|
.rhs_type = rhs_res,
|
|
.lhs = lhs,
|
|
.rhs = rhs,
|
|
} });
|
|
}
|
|
|
|
fn genCall(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const token = astgen.tree.nodeToken(node);
|
|
const token_tag = astgen.tree.tokenTag(token);
|
|
const token_loc = astgen.tree.tokenLoc(token);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
|
|
if (node_rhs == .none) {
|
|
std.debug.assert(token_tag == .ident);
|
|
|
|
const builtin_fn = std.meta.stringToEnum(BuiltinFn, token_loc.slice(astgen.tree.source)) orelse {
|
|
const decl = try astgen.findSymbol(scope, token);
|
|
switch (astgen.getInst(decl)) {
|
|
.@"fn" => return astgen.genFnCall(scope, node),
|
|
.@"struct" => return astgen.genStructConstruct(scope, decl, node),
|
|
else => {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"'{s}' cannot be called",
|
|
.{token_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
};
|
|
switch (builtin_fn) {
|
|
.all => return astgen.genBuiltinAllAny(scope, node, true),
|
|
.any => return astgen.genBuiltinAllAny(scope, node, false),
|
|
.select => return astgen.genBuiltinSelect(scope, node),
|
|
.abs => return astgen.genGenericUnaryBuiltin(scope, node, .abs, &.{ .u32, .i32 }, &.{ .f32, .f16 }, false, false),
|
|
.acos => return astgen.genGenericUnaryBuiltin(scope, node, .acos, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.acosh => return astgen.genGenericUnaryBuiltin(scope, node, .acosh, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.asin => return astgen.genGenericUnaryBuiltin(scope, node, .asin, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.asinh => return astgen.genGenericUnaryBuiltin(scope, node, .asinh, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.atan => return astgen.genGenericUnaryBuiltin(scope, node, .atan, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.atanh => return astgen.genGenericUnaryBuiltin(scope, node, .atanh, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.ceil => return astgen.genGenericUnaryBuiltin(scope, node, .ceil, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.cos => return astgen.genGenericUnaryBuiltin(scope, node, .cos, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.cosh => return astgen.genGenericUnaryBuiltin(scope, node, .cosh, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.countLeadingZeros => return astgen.genGenericUnaryBuiltin(scope, node, .count_leading_zeros, &.{ .u32, .i32 }, &.{}, false, false),
|
|
.countOneBits => return astgen.genGenericUnaryBuiltin(scope, node, .count_one_bits, &.{ .u32, .i32 }, &.{}, false, false),
|
|
.countTrailingZeros => return astgen.genGenericUnaryBuiltin(scope, node, .count_trailing_zeros, &.{ .u32, .i32 }, &.{}, false, false),
|
|
.degrees => return astgen.genGenericUnaryBuiltin(scope, node, .degrees, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.exp => return astgen.genGenericUnaryBuiltin(scope, node, .exp, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.exp2 => return astgen.genGenericUnaryBuiltin(scope, node, .exp2, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.firstLeadingBit => return astgen.genGenericUnaryBuiltin(scope, node, .first_leading_bit, &.{ .u32, .i32 }, &.{}, false, false),
|
|
.firstTrailingBit => return astgen.genGenericUnaryBuiltin(scope, node, .first_trailing_bit, &.{ .u32, .i32 }, &.{}, false, false),
|
|
.floor => return astgen.genGenericUnaryBuiltin(scope, node, .floor, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.fract => return astgen.genGenericUnaryBuiltin(scope, node, .fract, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.inverseSqrt => return astgen.genGenericUnaryBuiltin(scope, node, .inverse_sqrt, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.length => return astgen.genGenericUnaryBuiltin(scope, node, .length, &.{}, &.{ .f32, .f16 }, false, true),
|
|
.log => return astgen.genGenericUnaryBuiltin(scope, node, .log, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.log2 => return astgen.genGenericUnaryBuiltin(scope, node, .log2, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.quantizeToF16 => return astgen.genGenericUnaryBuiltin(scope, node, .quantize_to_F16, &.{}, &.{.f32}, false, false),
|
|
.radians => return astgen.genGenericUnaryBuiltin(scope, node, .radians, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.reverseBits => return astgen.genGenericUnaryBuiltin(scope, node, .reverseBits, &.{ .u32, .i32 }, &.{}, false, false),
|
|
.round => return astgen.genGenericUnaryBuiltin(scope, node, .round, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.saturate => return astgen.genGenericUnaryBuiltin(scope, node, .saturate, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.sign => return astgen.genGenericUnaryBuiltin(scope, node, .sign, &.{ .u32, .i32 }, &.{.f16}, false, false),
|
|
.sin => return astgen.genGenericUnaryBuiltin(scope, node, .sin, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.sinh => return astgen.genGenericUnaryBuiltin(scope, node, .sinh, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.sqrt => return astgen.genGenericUnaryBuiltin(scope, node, .sqrt, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.tan => return astgen.genGenericUnaryBuiltin(scope, node, .tan, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.tanh => return astgen.genGenericUnaryBuiltin(scope, node, .tanh, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.trunc => return astgen.genGenericUnaryBuiltin(scope, node, .trunc, &.{}, &.{ .f32, .f16 }, false, false),
|
|
.normalize => return astgen.genGenericUnaryBuiltin(scope, node, .normalize, &.{}, &.{ .f32, .f16 }, true, false),
|
|
.min => return astgen.genGenericBinaryBuiltin(scope, node, .min, .any_in_any_out, true),
|
|
.max => return astgen.genGenericBinaryBuiltin(scope, node, .max, .any_in_any_out, true),
|
|
.atan2 => return astgen.genGenericBinaryBuiltin(scope, node, .atan2, .any_in_any_out, true),
|
|
.distance => return astgen.genGenericBinaryBuiltin(scope, node, .distance, .any_in_scalar_out, false),
|
|
.dot => return astgen.genGenericBinaryBuiltin(scope, node, .dot, .vector_in_scalar_out, false),
|
|
.pow => return astgen.genGenericBinaryBuiltin(scope, node, .pow, .any_in_any_out, false),
|
|
.step => return astgen.genGenericBinaryBuiltin(scope, node, .step, .any_in_any_out, false),
|
|
.smoothstep => return astgen.genGenericFloatTripleBuiltin(scope, node, .smoothstep, false),
|
|
.clamp => return astgen.genGenericFloatTripleBuiltin(scope, node, .clamp, false),
|
|
.mix => return astgen.genGenericFloatTripleBuiltin(scope, node, .mix, true),
|
|
.dpdx => return astgen.genDerivativeBuiltin(scope, node, .dpdx),
|
|
.dpdxCoarse => return astgen.genDerivativeBuiltin(scope, node, .dpdx_coarse),
|
|
.dpdxFine => return astgen.genDerivativeBuiltin(scope, node, .dpdx_fine),
|
|
.dpdy => return astgen.genDerivativeBuiltin(scope, node, .dpdy),
|
|
.dpdyCoarse => return astgen.genDerivativeBuiltin(scope, node, .dpdy_coarse),
|
|
.dpdyFine => return astgen.genDerivativeBuiltin(scope, node, .dpdy_fine),
|
|
.fwidth => return astgen.genDerivativeBuiltin(scope, node, .fwidth),
|
|
.fwidthCoarse => return astgen.genDerivativeBuiltin(scope, node, .fwidth_coarse),
|
|
.fwidthFine => return astgen.genDerivativeBuiltin(scope, node, .fwidth_fine),
|
|
.arrayLength => return astgen.genArrayLengthBuiltin(scope, node),
|
|
.textureSample => return astgen.genTextureSampleBuiltin(scope, node),
|
|
.textureSampleLevel => return astgen.genTextureSampleLevelBuiltin(scope, node),
|
|
.textureSampleGrad => return astgen.genTextureSampleGradBuiltin(scope, node),
|
|
.textureDimensions => return astgen.genTextureDimensionsBuiltin(scope, node),
|
|
.textureLoad => return astgen.genTextureLoadBuiltin(scope, node),
|
|
.textureStore => return astgen.genTextureStoreBuiltin(scope, node),
|
|
.workgroupBarrier => return astgen.genSimpleBuiltin(.workgroup_barrier),
|
|
.storageBarrier => return astgen.genSimpleBuiltin(.storage_barrier),
|
|
else => {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"TODO: unimplemented builtin '{s}'",
|
|
.{token_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
switch (token_tag) {
|
|
.k_bool => {
|
|
if (node_lhs == .none) {
|
|
return astgen.addInst(.{ .bool = .{ .value = .{ .literal = false } } });
|
|
}
|
|
|
|
const arg_node = astgen.tree.spanToList(node_lhs)[0];
|
|
const expr = try astgen.genExpr(scope, arg_node);
|
|
const expr_res = try astgen.resolve(expr);
|
|
switch (astgen.getInst(expr_res)) {
|
|
.bool => return expr,
|
|
.int, .float => return astgen.addInst(.{ .bool = .{
|
|
.value = .{
|
|
.cast = .{
|
|
.value = expr,
|
|
.type = expr_res,
|
|
},
|
|
},
|
|
} }),
|
|
else => {},
|
|
}
|
|
|
|
try astgen.errors.add(node_loc, "cannot construct bool", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.k_u32, .k_i32 => {
|
|
const ty: Inst.Int.Type = switch (token_tag) {
|
|
.k_u32 => .u32,
|
|
.k_i32 => .i32,
|
|
else => unreachable,
|
|
};
|
|
|
|
if (node_lhs == .none) {
|
|
const zero_value = try astgen.addValue(Inst.Int.Value, .{ .literal = 0 });
|
|
return astgen.addInst(.{ .int = .{ .value = zero_value, .type = ty } });
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len > 1) {
|
|
try astgen.errors.add(node_loc, "too many arguments", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const arg_node = arg_nodes[0];
|
|
const expr = try astgen.genExpr(scope, arg_node);
|
|
const expr_res = try astgen.resolve(expr);
|
|
|
|
switch (astgen.getInst(expr_res)) {
|
|
.bool, .float => {},
|
|
.int => |int| if (int.type == ty) return expr,
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
const value = try astgen.addValue(Inst.Int.Value, .{
|
|
.cast = .{
|
|
.value = expr,
|
|
.type = expr_res,
|
|
},
|
|
});
|
|
|
|
return astgen.addInst(.{ .int = .{ .value = value, .type = ty } });
|
|
},
|
|
.k_f32, .k_f16 => {
|
|
const ty: Inst.Float.Type = switch (token_tag) {
|
|
.k_f32 => .f32,
|
|
.k_f16 => .f16,
|
|
else => unreachable,
|
|
};
|
|
|
|
if (node_lhs == .none) {
|
|
const zero_value = try astgen.addValue(Inst.Float.Value, .{ .literal = 0 });
|
|
return astgen.addInst(.{ .float = .{ .value = zero_value, .type = ty } });
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len > 1) {
|
|
try astgen.errors.add(node_loc, "too many arguments", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const arg_node = arg_nodes[0];
|
|
const expr = try astgen.genExpr(scope, arg_node);
|
|
const expr_res = try astgen.resolve(expr);
|
|
|
|
switch (astgen.getInst(expr_res)) {
|
|
.bool, .int => {},
|
|
.float => |float| if (float.type == ty) return expr,
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
const value = try astgen.addValue(Inst.Float.Value, .{
|
|
.cast = .{
|
|
.value = expr,
|
|
.type = expr_res,
|
|
},
|
|
});
|
|
|
|
return astgen.addInst(.{ .float = .{ .value = value, .type = ty } });
|
|
},
|
|
.k_vec2, .k_vec3, .k_vec4 => {
|
|
const elem_type_node = astgen.tree.nodeLHS(node_rhs);
|
|
const size: Inst.Vector.Size = switch (token_tag) {
|
|
.k_vec2 => .two,
|
|
.k_vec3 => .three,
|
|
.k_vec4 => .four,
|
|
else => unreachable,
|
|
};
|
|
var elem_type = InstIndex.none;
|
|
|
|
if (elem_type_node != .none) {
|
|
elem_type = try astgen.genType(scope, elem_type_node);
|
|
switch (astgen.getInst(elem_type)) {
|
|
.bool, .int, .float => {},
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(elem_type_node),
|
|
"invalid vector component type",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"must be 'i32', 'u32', 'f32', 'f16' or 'bool'",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
if (node_lhs == .none) {
|
|
if (elem_type_node == .none) {
|
|
try astgen.errors.add(node_loc, "cannot infer vector type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.vector = .{
|
|
.elem_type = elem_type,
|
|
.size = size,
|
|
.value = .none,
|
|
},
|
|
});
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
var args: [4]InstIndex = undefined;
|
|
var cast = InstIndex.none;
|
|
|
|
var capacity = @intFromEnum(size);
|
|
for (arg_nodes) |arg_node| {
|
|
const i = @intFromEnum(size) - capacity;
|
|
const arg = try astgen.genExpr(scope, arg_node);
|
|
const arg_loc = astgen.tree.nodeLoc(arg_node);
|
|
const arg_res = try astgen.resolve(arg);
|
|
|
|
if (capacity == 0) {
|
|
try astgen.errors.add(arg_loc, "doesn't fit in this vector", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (astgen.getInst(arg_res)) {
|
|
.vector => |arg_vec| {
|
|
if (elem_type == .none) {
|
|
elem_type = arg_vec.elem_type;
|
|
} else if (!astgen.eql(arg_vec.elem_type, elem_type)) {
|
|
cast = arg_vec.elem_type;
|
|
}
|
|
|
|
if (capacity >= @intFromEnum(arg_vec.size)) {
|
|
for (0..@intFromEnum(arg_vec.size)) |component_i| {
|
|
args[i + component_i] =
|
|
astgen.resolveVectorValue(arg, @intCast(component_i)) orelse
|
|
try astgen.addInst(.{
|
|
.swizzle_access = .{
|
|
.base = arg,
|
|
.type = astgen.getInst(arg_res).vector.elem_type,
|
|
.size = .one,
|
|
.pattern = [_]Inst.SwizzleAccess.Component{
|
|
@enumFromInt(component_i),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
capacity -= @intFromEnum(arg_vec.size);
|
|
} else {
|
|
try astgen.errors.add(arg_loc, "doesn't fit in this vector", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
.bool, .int, .float => {
|
|
var cast_arg = false;
|
|
if (elem_type == .none) {
|
|
elem_type = arg_res;
|
|
} else if (!astgen.eql(arg_res, elem_type)) {
|
|
cast_arg = true;
|
|
}
|
|
|
|
if (cast_arg) {
|
|
switch (astgen.getInst(elem_type)) {
|
|
.int => |int| {
|
|
const arg_val = try astgen.addValue(
|
|
Inst.Int.Value,
|
|
.{ .cast = .{ .type = arg_res, .value = arg } },
|
|
);
|
|
args[i] = try astgen.addInst(.{ .int = .{
|
|
.type = int.type,
|
|
.value = arg_val,
|
|
} });
|
|
},
|
|
.float => |float| {
|
|
const arg_val = try astgen.addValue(
|
|
Inst.Float.Value,
|
|
.{ .cast = .{ .type = arg_res, .value = arg } },
|
|
);
|
|
args[i] = try astgen.addInst(.{ .float = .{
|
|
.type = float.type,
|
|
.value = arg_val,
|
|
} });
|
|
},
|
|
.bool => {
|
|
args[i] = try astgen.addInst(.{ .bool = .{
|
|
.value = .{ .cast = .{ .type = arg_res, .value = arg } },
|
|
} });
|
|
},
|
|
else => unreachable,
|
|
}
|
|
} else {
|
|
args[i] = arg;
|
|
}
|
|
|
|
if (arg_nodes.len == 1) {
|
|
@memset(args[i + 1 .. @intFromEnum(size)], args[i]);
|
|
capacity = 1;
|
|
}
|
|
|
|
capacity -= 1;
|
|
},
|
|
else => {
|
|
try astgen.errors.add(arg_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
if (capacity != 0) {
|
|
try astgen.errors.add(node_loc, "arguments doesn't satisfy vector capacity", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const value = try astgen.addValue(
|
|
Inst.Vector.Value,
|
|
if (cast == .none)
|
|
.{ .literal = args }
|
|
else
|
|
.{ .cast = .{ .type = cast, .value = args } },
|
|
);
|
|
|
|
return astgen.addInst(.{
|
|
.vector = .{
|
|
.elem_type = elem_type,
|
|
.size = size,
|
|
.value = value,
|
|
},
|
|
});
|
|
},
|
|
.k_mat2x2,
|
|
.k_mat2x3,
|
|
.k_mat2x4,
|
|
.k_mat3x2,
|
|
.k_mat3x3,
|
|
.k_mat3x4,
|
|
.k_mat4x2,
|
|
.k_mat4x3,
|
|
.k_mat4x4,
|
|
=> {
|
|
const elem_type_node = astgen.tree.nodeLHS(node_rhs);
|
|
const cols = matrixCols(token_tag);
|
|
const rows = matrixRows(token_tag);
|
|
var elem_type = InstIndex.none;
|
|
|
|
if (elem_type_node != .none) {
|
|
elem_type = try astgen.genType(scope, elem_type_node);
|
|
if (astgen.getInst(elem_type) != .float) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(elem_type_node),
|
|
"invalid matrix component type",
|
|
.{},
|
|
try astgen.errors.createNote(null, "must be 'f32', 'f16'", .{}),
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
if (node_lhs == .none) {
|
|
if (elem_type_node == .none) {
|
|
try astgen.errors.add(node_loc, "cannot infer matrix type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.matrix = .{
|
|
.elem_type = elem_type,
|
|
.rows = rows,
|
|
.cols = cols,
|
|
.value = .none,
|
|
},
|
|
});
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
var args: [4]InstIndex = undefined;
|
|
|
|
var capacity = @intFromEnum(cols);
|
|
for (arg_nodes) |arg_node| {
|
|
const i = @intFromEnum(cols) - capacity;
|
|
const arg = try astgen.genExpr(scope, arg_node);
|
|
const arg_loc = astgen.tree.nodeLoc(arg_node);
|
|
const arg_res = try astgen.resolve(arg);
|
|
|
|
if (capacity == 0) {
|
|
try astgen.errors.add(arg_loc, "doesn't fit in this matrix", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (astgen.getInst(arg_res)) {
|
|
.matrix => |arg_mat| {
|
|
if (elem_type == .none) {
|
|
elem_type = arg_mat.elem_type;
|
|
} else {
|
|
if (!try astgen.coerce(arg_mat.elem_type, elem_type)) {
|
|
try astgen.errors.add(arg_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
if (arg_nodes.len == 1 and arg_mat.cols == cols and arg_mat.rows == rows) {
|
|
return arg;
|
|
}
|
|
|
|
try astgen.errors.add(arg_loc, "invalid argument", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.vector => |arg_vec| {
|
|
if (elem_type == .none) {
|
|
elem_type = arg_vec.elem_type;
|
|
} else {
|
|
if (!try astgen.coerce(arg_vec.elem_type, elem_type)) {
|
|
try astgen.errors.add(arg_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
if (@intFromEnum(arg_vec.size) != @intFromEnum(rows)) {
|
|
try astgen.errors.add(arg_loc, "invalid argument", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
args[i] = arg;
|
|
capacity -= 1;
|
|
},
|
|
.float => {
|
|
if (elem_type == .none) {
|
|
elem_type = arg_res;
|
|
}
|
|
|
|
if (arg_nodes.len != 1) {
|
|
try astgen.errors.add(arg_loc, "invalid argument", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
for (args[0..@intFromEnum(cols)]) |*arg_col| {
|
|
var arg_vec_value: [4]InstIndex = undefined;
|
|
@memset(arg_vec_value[0..@intFromEnum(rows)], arg);
|
|
|
|
const arg_vec = try astgen.addInst(.{
|
|
.vector = .{
|
|
.elem_type = elem_type,
|
|
.size = rows,
|
|
.value = try astgen.addValue(Inst.Vector.Value, .{ .literal = arg_vec_value }),
|
|
},
|
|
});
|
|
|
|
arg_col.* = arg_vec;
|
|
capacity -= 1;
|
|
}
|
|
},
|
|
else => {
|
|
try astgen.errors.add(arg_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
if (capacity != 0) {
|
|
try astgen.errors.add(node_loc, "arguments doesn't satisfy matrix capacity", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.matrix = .{
|
|
.elem_type = elem_type,
|
|
.cols = cols,
|
|
.rows = rows,
|
|
.value = try astgen.addValue(Inst.Matrix.Value, args),
|
|
},
|
|
});
|
|
},
|
|
.k_array => {
|
|
if (node_lhs == .none) {
|
|
return astgen.genArray(scope, node_rhs, .none);
|
|
}
|
|
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
var arg1_res = InstIndex.none;
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
for (arg_nodes, 0..) |arg_node, i| {
|
|
const arg = try astgen.genExpr(scope, arg_node);
|
|
const arg_res = try astgen.resolve(arg);
|
|
if (i == 0) {
|
|
arg1_res = arg_res;
|
|
} else if (!try astgen.coerce(arg_res, arg1_res)) {
|
|
try astgen.errors.add(node_loc, "cannot construct array", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
try astgen.scratch.append(astgen.allocator, arg);
|
|
}
|
|
|
|
const args = try astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
return astgen.genArray(scope, node_rhs, args);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn resolveVectorValue(astgen: *AstGen, vector_idx: InstIndex, value_idx: u3) ?InstIndex {
|
|
return switch (astgen.getInst(vector_idx)) {
|
|
.vector => |vector| switch (astgen.getValue(Inst.Vector.Value, vector.value.?)) {
|
|
.literal => |literal| literal[value_idx],
|
|
.cast => |cast| cast.value[value_idx],
|
|
},
|
|
inline .swizzle_access, .index_access => |access| astgen.resolveVectorValue(access.base, value_idx),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
fn genReturn(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
|
|
var fn_scope = findFnScope(scope);
|
|
var value = InstIndex.none;
|
|
if (node_lhs != .none) {
|
|
if (fn_scope.tag.@"fn".return_type == .none) {
|
|
try astgen.errors.add(node_loc, "cannot return value", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
value = try astgen.genExpr(scope, node_lhs);
|
|
const value_res = try astgen.resolve(value);
|
|
if (!try astgen.coerce(value_res, fn_scope.tag.@"fn".return_type)) {
|
|
try astgen.errors.add(node_loc, "return type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
} else {
|
|
if (fn_scope.tag.@"fn".return_type != .none) {
|
|
try astgen.errors.add(node_loc, "return value not specified", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
fn_scope.tag.@"fn".returned = true;
|
|
return astgen.addInst(.{ .@"return" = value });
|
|
}
|
|
|
|
fn findFnScope(scope: *Scope) *Scope {
|
|
var s = scope;
|
|
while (true) {
|
|
switch (s.tag) {
|
|
.root => unreachable,
|
|
.@"fn" => return s,
|
|
.block,
|
|
.loop,
|
|
.continuing,
|
|
.switch_case,
|
|
.@"if",
|
|
.@"for",
|
|
=> s = s.parent,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn genFnCall(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const token = astgen.tree.nodeToken(node);
|
|
const decl = try astgen.findSymbol(scope, token);
|
|
if (astgen.tree.nodeRHS(node) != .none) {
|
|
try astgen.errors.add(node_loc, "expected a function", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
var args = RefIndex.none;
|
|
if (node_lhs != .none) {
|
|
const params = astgen.refToList(astgen.getInst(decl).@"fn".params);
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (params.len != arg_nodes.len) {
|
|
try astgen.errors.add(node_loc, "function params count mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
for (arg_nodes, 0..) |arg_node, i| {
|
|
const arg = try astgen.genExpr(scope, arg_node);
|
|
const arg_res = try astgen.resolve(arg);
|
|
if (try astgen.coerce(astgen.getInst(params[i]).fn_param.type, arg_res)) {
|
|
try astgen.scratch.append(astgen.allocator, arg);
|
|
} else {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(arg_node),
|
|
"value and member type mismatch",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
args = try astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
} else {
|
|
if (astgen.getInst(decl).@"fn".params != .none) {
|
|
try astgen.errors.add(node_loc, "function params count mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
for (astgen.refToList(astgen.getInst(decl).@"fn".global_var_refs)) |var_inst_idx| {
|
|
try astgen.global_var_refs.put(astgen.allocator, var_inst_idx, {});
|
|
}
|
|
|
|
return astgen.addInst(.{ .call = .{ .@"fn" = decl, .args = args } });
|
|
}
|
|
|
|
fn genStructConstruct(astgen: *AstGen, scope: *Scope, decl: InstIndex, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
|
|
const scratch_top = astgen.scratch.items.len;
|
|
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
|
|
|
const struct_members = astgen.refToList(astgen.getInst(decl).@"struct".members);
|
|
if (node_lhs != .none) {
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (struct_members.len != arg_nodes.len) {
|
|
try astgen.errors.add(node_loc, "struct members count mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
for (arg_nodes, 0..) |arg_node, i| {
|
|
const arg = try astgen.genExpr(scope, arg_node);
|
|
const arg_res = try astgen.resolve(arg);
|
|
if (try astgen.coerce(arg_res, astgen.getInst(struct_members[i]).struct_member.type)) {
|
|
try astgen.scratch.append(astgen.allocator, arg);
|
|
} else {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(arg_node),
|
|
"value and member type mismatch",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
} else {
|
|
if (struct_members.len != 0) {
|
|
try astgen.errors.add(node_loc, "struct members count mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
const members = try astgen.addRefList(astgen.scratch.items[scratch_top..]);
|
|
return astgen.addInst(.{
|
|
.struct_construct = .{
|
|
.@"struct" = decl,
|
|
.members = members,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genBitcast(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const node_lhs_loc = astgen.tree.nodeLoc(node_lhs);
|
|
const node_rhs_loc = astgen.tree.nodeLoc(node_rhs);
|
|
const lhs = try astgen.genType(scope, node_lhs);
|
|
const lhs_inst = astgen.getInst(lhs);
|
|
const rhs = try astgen.genExpr(scope, node_rhs);
|
|
const rhs_res = try astgen.resolve(rhs);
|
|
const rhs_res_inst = astgen.getInst(rhs_res);
|
|
var result_type = InstIndex.none;
|
|
|
|
const lhs_is_32bit = switch (lhs_inst) {
|
|
.int => |int| int.type == .u32 or int.type == .i32,
|
|
.float => |float| float.type == .f32,
|
|
else => false,
|
|
};
|
|
const rhs_is_32bit = switch (rhs_res_inst) {
|
|
.int => |int| int.type == .u32 or int.type == .i32,
|
|
.float => |float| float.type == .f32,
|
|
else => false,
|
|
};
|
|
|
|
if (lhs_is_32bit) {
|
|
if (rhs_is_32bit) {
|
|
// bitcast<T>(T) -> T
|
|
// bitcast<T>(S) -> T
|
|
result_type = lhs;
|
|
} else if (rhs_res_inst == .vector) {
|
|
const rhs_vec_type = astgen.getInst(rhs_res_inst.vector.elem_type);
|
|
|
|
if (rhs_res_inst.vector.size == .two and
|
|
rhs_vec_type == .float and rhs_vec_type.float.type == .f16)
|
|
{
|
|
// bitcast<T>(vec2<f16>) -> T
|
|
result_type = lhs;
|
|
}
|
|
}
|
|
} else if (lhs_inst == .vector) {
|
|
if (rhs_is_32bit) {
|
|
const lhs_vec_type = astgen.getInst(lhs_inst.vector.elem_type);
|
|
|
|
if (lhs_inst.vector.size == .two and
|
|
lhs_vec_type == .float and lhs_vec_type.float.type == .f16)
|
|
{
|
|
// bitcast<vec2<f16>>(T) -> vec2<f16>
|
|
result_type = lhs;
|
|
}
|
|
} else if (rhs_res_inst == .vector) {
|
|
const lhs_vec_type = astgen.getInst(lhs_inst.vector.elem_type);
|
|
const rhs_vec_type = astgen.getInst(rhs_res_inst.vector.elem_type);
|
|
|
|
const lhs_vec_is_32bit = switch (lhs_vec_type) {
|
|
.int => |int| int.type == .u32 or int.type == .i32,
|
|
.float => |float| float.type == .f32,
|
|
else => false,
|
|
};
|
|
const rhs_vec_is_32bit = switch (rhs_vec_type) {
|
|
.int => |int| int.type == .u32 or int.type == .i32,
|
|
.float => |float| float.type == .f32,
|
|
else => false,
|
|
};
|
|
|
|
if (lhs_vec_is_32bit) {
|
|
if (rhs_vec_is_32bit) {
|
|
if (lhs_inst.vector.size == rhs_res_inst.vector.size) {
|
|
if (lhs_inst.vector.elem_type == rhs_res_inst.vector.elem_type) {
|
|
// bitcast<vecN<T>>(vecN<T>) -> vecN<T>
|
|
result_type = lhs;
|
|
} else {
|
|
// bitcast<vecN<T>>(vecN<S>) -> T
|
|
result_type = lhs_inst.vector.elem_type;
|
|
}
|
|
}
|
|
} else if (rhs_vec_type == .float and rhs_vec_type.float.type == .f16) {
|
|
if (lhs_inst.vector.size == .two and
|
|
rhs_res_inst.vector.size == .four)
|
|
{
|
|
// bitcast<vec2<T>>(vec4<f16>) -> vec2<T>
|
|
result_type = lhs;
|
|
}
|
|
}
|
|
} else if (lhs_vec_type == .float and lhs_vec_type.float.type == .f16) {
|
|
if (rhs_res_inst.vector.size == .two and
|
|
lhs_inst.vector.size == .four)
|
|
{
|
|
// bitcast<vec4<f16>>(vec2<T>) -> vec4<f16>
|
|
result_type = lhs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result_type != .none) {
|
|
const inst = try astgen.addInst(.{
|
|
.bitcast = .{
|
|
.type = lhs,
|
|
.expr = rhs,
|
|
.result_type = result_type,
|
|
},
|
|
});
|
|
return inst;
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
node_rhs_loc,
|
|
"cannot cast '{s}' into '{s}'",
|
|
.{ node_rhs_loc.slice(astgen.tree.source), node_lhs_loc.slice(astgen.tree.source) },
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genBuiltinAllAny(astgen: *AstGen, scope: *Scope, node: NodeIndex, all: bool) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 1) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, arg_nodes.len);
|
|
}
|
|
|
|
const arg = try astgen.genExpr(scope, arg_nodes[0]);
|
|
const arg_res = try astgen.resolve(arg);
|
|
switch (astgen.getInst(arg_res)) {
|
|
.bool => return arg,
|
|
.vector => |vec| {
|
|
if (astgen.getInst(vec.elem_type) != .bool) {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const result_type = try astgen.addInst(.{ .bool = .{ .value = null } });
|
|
return astgen.addInst(.{ .unary_intrinsic = .{
|
|
.op = if (all) .all else .any,
|
|
.expr = arg,
|
|
.result_type = result_type,
|
|
} });
|
|
},
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn genBuiltinSelect(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 3) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, arg_nodes.len);
|
|
}
|
|
|
|
const arg1 = try astgen.genExpr(scope, arg_nodes[0]);
|
|
const arg2 = try astgen.genExpr(scope, arg_nodes[1]);
|
|
const arg3 = try astgen.genExpr(scope, arg_nodes[2]);
|
|
const arg1_res = try astgen.resolve(arg1);
|
|
const arg2_res = try astgen.resolve(arg2);
|
|
const arg3_res = try astgen.resolve(arg3);
|
|
|
|
if (!try astgen.coerce(arg2_res, arg1_res)) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (astgen.getInst(arg3_res)) {
|
|
.bool => {
|
|
return astgen.addInst(.{
|
|
.select = .{
|
|
.type = arg1_res,
|
|
.true = arg1,
|
|
.false = arg2,
|
|
.cond = arg3,
|
|
},
|
|
});
|
|
},
|
|
.vector => |vec| {
|
|
if (astgen.getInst(vec.elem_type) != .bool) {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (astgen.getInst(arg1_res) != .vector) {
|
|
try astgen.errors.add(node_loc, "'true' and 'false' must be vector", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.select = .{
|
|
.type = arg1_res,
|
|
.true = arg1,
|
|
.false = arg2,
|
|
.cond = arg2,
|
|
},
|
|
});
|
|
},
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn genDerivativeBuiltin(
|
|
astgen: *AstGen,
|
|
scope: *Scope,
|
|
node: NodeIndex,
|
|
comptime op: Inst.UnaryIntrinsic.Op,
|
|
) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
|
|
if (astgen.current_fn_scope.tag.@"fn".stage != .fragment) {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"invalid builtin in {s} stage",
|
|
.{@tagName(astgen.current_fn_scope.tag.@"fn".stage)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 1) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, arg_nodes.len);
|
|
}
|
|
|
|
const arg = try astgen.genExpr(scope, arg_nodes[0]);
|
|
const arg_res = try astgen.resolve(arg);
|
|
const inst = Inst{ .unary_intrinsic = .{
|
|
.op = op,
|
|
.expr = arg,
|
|
.result_type = arg_res,
|
|
} };
|
|
switch (astgen.getInst(arg_res)) {
|
|
.float => |float| {
|
|
if (float.type == .f32) {
|
|
return astgen.addInst(inst);
|
|
}
|
|
},
|
|
.vector => |vec| {
|
|
switch (astgen.getInst(vec.elem_type)) {
|
|
.float => |float| {
|
|
if (float.type == .f32) {
|
|
return astgen.addInst(inst);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genGenericUnaryBuiltin(
|
|
astgen: *AstGen,
|
|
scope: *Scope,
|
|
node: NodeIndex,
|
|
comptime op: Inst.UnaryIntrinsic.Op,
|
|
comptime int_limit: []const Inst.Int.Type,
|
|
comptime float_limit: []const Inst.Float.Type,
|
|
comptime vector_only: bool,
|
|
comptime scalar_result: bool,
|
|
) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 1) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, arg_nodes.len);
|
|
}
|
|
|
|
const arg = try astgen.genExpr(scope, arg_nodes[0]);
|
|
const arg_res = try astgen.resolve(arg);
|
|
var result_type = arg_res;
|
|
|
|
switch (astgen.getInst(arg_res)) {
|
|
.int => |int| if (vector_only or indexOf(Inst.Int.Type, int_limit, int.type) == null) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.float => |float| if (vector_only or indexOf(Inst.Float.Type, float_limit, float.type) == null) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.vector => |vec| {
|
|
switch (astgen.getInst(vec.elem_type)) {
|
|
.bool => {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.int => |int| if (indexOf(Inst.Int.Type, int_limit, int.type) == null) {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.float => |float| if (indexOf(Inst.Float.Type, float_limit, float.type) == null) {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (scalar_result) {
|
|
result_type = vec.elem_type;
|
|
}
|
|
},
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.unary_intrinsic = .{
|
|
.op = op,
|
|
.expr = arg,
|
|
.result_type = result_type,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genGenericBinaryBuiltin(
|
|
astgen: *AstGen,
|
|
scope: *Scope,
|
|
node: NodeIndex,
|
|
comptime op: Inst.BinaryIntrinsic.Op,
|
|
comptime form: enum { any_in_any_out, any_in_scalar_out, vector_in_scalar_out },
|
|
comptime allow_int: bool,
|
|
) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 2, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 2) {
|
|
return astgen.failArgCountMismatch(node_loc, 2, arg_nodes.len);
|
|
}
|
|
|
|
const arg1 = try astgen.genExpr(scope, arg_nodes[0]);
|
|
const arg2 = try astgen.genExpr(scope, arg_nodes[1]);
|
|
const arg1_res = try astgen.resolve(arg1);
|
|
const arg2_res = try astgen.resolve(arg2);
|
|
var result_type = arg1_res;
|
|
|
|
switch (astgen.getInst(arg1_res)) {
|
|
.float => if (form == .vector_in_scalar_out) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.int => if (!allow_int or form == .vector_in_scalar_out) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.vector => |vec| {
|
|
switch (astgen.getInst(vec.elem_type)) {
|
|
.bool => {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
.int => if (!allow_int) {
|
|
try astgen.errors.add(node_loc, "invalid vector element type", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
switch (form) {
|
|
.any_in_scalar_out, .vector_in_scalar_out => result_type = vec.elem_type,
|
|
.any_in_any_out => {},
|
|
}
|
|
},
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
if (!try astgen.coerce(arg2_res, arg1_res)) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
return astgen.addInst(.{ .binary_intrinsic = .{
|
|
.op = op,
|
|
.lhs = arg1,
|
|
.rhs = arg2,
|
|
.lhs_type = arg1_res,
|
|
.rhs_type = arg2_res,
|
|
.result_type = result_type,
|
|
} });
|
|
}
|
|
|
|
fn genGenericFloatTripleBuiltin(
|
|
astgen: *AstGen,
|
|
scope: *Scope,
|
|
node: NodeIndex,
|
|
comptime op: Inst.TripleIntrinsic.Op,
|
|
comptime scalar_float_arg3: bool,
|
|
) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 3) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, arg_nodes.len);
|
|
}
|
|
|
|
const a1 = try astgen.genExpr(scope, arg_nodes[0]);
|
|
const a2 = try astgen.genExpr(scope, arg_nodes[1]);
|
|
const a3 = try astgen.genExpr(scope, arg_nodes[2]);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a3_res = try astgen.resolve(a3);
|
|
|
|
if (!try astgen.coerce(a2_res, a1_res)) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (scalar_float_arg3) {
|
|
switch (astgen.getInst(a3_res)) {
|
|
.float => {},
|
|
else => {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
} else {
|
|
if (!try astgen.coerce(a3_res, a1_res)) {
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
if (astgen.getInst(a1_res) == .float or
|
|
(astgen.getInst(a1_res) == .vector and
|
|
astgen.getInst(astgen.getInst(a1_res).vector.elem_type) == .float))
|
|
{
|
|
return astgen.addInst(.{
|
|
.triple_intrinsic = .{
|
|
.op = op,
|
|
.result_type = a1_res,
|
|
.a1_type = a1_res,
|
|
.a2_type = a2_res,
|
|
.a3_type = a3_res,
|
|
.a1 = a1,
|
|
.a2 = a2,
|
|
.a3 = a3,
|
|
},
|
|
});
|
|
}
|
|
|
|
try astgen.errors.add(node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genArrayLengthBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
astgen.has_array_length = true;
|
|
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len != 1) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, arg_nodes.len);
|
|
}
|
|
|
|
const arg_node = arg_nodes[0];
|
|
const arg_node_loc = astgen.tree.nodeLoc(arg_node);
|
|
const arg = try astgen.genExpr(scope, arg_node);
|
|
const arg_res = try astgen.resolve(arg);
|
|
const arg_res_inst = astgen.getInst(arg_res);
|
|
|
|
if (arg_res_inst == .ptr_type) {
|
|
const ptr_elem_inst = astgen.getInst(arg_res_inst.ptr_type.elem_type);
|
|
if (ptr_elem_inst == .array) {
|
|
const result_type = try astgen.addInst(.{ .int = .{ .type = .u32, .value = null } });
|
|
return astgen.addInst(.{ .unary_intrinsic = .{
|
|
.op = .array_length,
|
|
.expr = arg,
|
|
.result_type = result_type,
|
|
} });
|
|
}
|
|
}
|
|
|
|
try astgen.errors.add(arg_node_loc, "type mismatch", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genTextureDimensionsBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len < 1) {
|
|
return astgen.failArgCountMismatch(node_loc, 1, arg_nodes.len);
|
|
}
|
|
|
|
const a1_node = arg_nodes[0];
|
|
const a1_node_loc = astgen.tree.nodeLoc(a1_node);
|
|
const a1 = try astgen.genExpr(scope, a1_node);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a1_inst = astgen.getInst(a1_res);
|
|
|
|
if (a1_inst != .texture_type) {
|
|
try astgen.errors.add(a1_node_loc, "expected a texture type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
var level = InstIndex.none;
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_2d,
|
|
.sampled_2d_array,
|
|
.sampled_cube,
|
|
.sampled_cube_array,
|
|
.multisampled_2d,
|
|
.multisampled_depth_2d,
|
|
.storage_2d,
|
|
.storage_2d_array,
|
|
.depth_2d,
|
|
.depth_2d_array,
|
|
.depth_cube,
|
|
.depth_cube_array,
|
|
=> {
|
|
if (arg_nodes.len > 1) {
|
|
const a2_node = arg_nodes[1];
|
|
const a2_node_loc = astgen.tree.nodeLoc(a2_node);
|
|
const a2 = try astgen.genExpr(scope, a2_node);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a2_inst = astgen.getInst(a2_res);
|
|
|
|
if (a2_inst != .int) {
|
|
try astgen.errors.add(a2_node_loc, "expected i32 or u32", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
level = a2;
|
|
}
|
|
},
|
|
else => {
|
|
try astgen.errors.add(a1_node_loc, "invalid texture", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
const result_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = try astgen.addInst(.{ .int = .{ .type = .u32, .value = null } }),
|
|
.size = .two,
|
|
.value = null,
|
|
} });
|
|
return astgen.addInst(.{ .texture_dimension = .{
|
|
.kind = a1_inst.texture_type.kind,
|
|
.texture = a1,
|
|
.level = level,
|
|
.result_type = result_type,
|
|
} });
|
|
}
|
|
|
|
fn genTextureLoadBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len < 3) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, arg_nodes.len);
|
|
}
|
|
|
|
const a1_node = arg_nodes[0];
|
|
const a1_node_loc = astgen.tree.nodeLoc(a1_node);
|
|
const a1 = try astgen.genExpr(scope, a1_node);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a1_inst = astgen.getInst(a1_res);
|
|
|
|
const a2_node = arg_nodes[1];
|
|
const a2_node_loc = astgen.tree.nodeLoc(a2_node);
|
|
const a2 = try astgen.genExpr(scope, a2_node);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a2_inst = astgen.getInst(a2_res);
|
|
|
|
const a3_node = arg_nodes[2];
|
|
const a3_node_loc = astgen.tree.nodeLoc(a3_node);
|
|
const a3 = try astgen.genExpr(scope, a3_node);
|
|
const a3_res = try astgen.resolve(a3);
|
|
const a3_inst = astgen.getInst(a3_res);
|
|
|
|
if (a1_inst != .texture_type) {
|
|
try astgen.errors.add(a1_node_loc, "expected a texture type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a2_inst != .vector or a2_inst.vector.size != .two or astgen.getInst(a2_inst.vector.elem_type) != .int) {
|
|
try astgen.errors.add(a2_node_loc, "expected vec2<i32> or vec2<u32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a3_inst != .int) {
|
|
try astgen.errors.add(a3_node_loc, "expected i32 or u32", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const result_type = switch (a1_inst.texture_type.kind) {
|
|
.sampled_2d => try astgen.addInst(.{ .vector = .{
|
|
.elem_type = a1_inst.texture_type.elem_type,
|
|
.size = .four,
|
|
.value = null,
|
|
} }),
|
|
.depth_2d => try astgen.addInst(.{ .float = .{ .type = .f32, .value = null } }),
|
|
else => {
|
|
try astgen.errors.add(a1_node_loc, "invalid texture", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
};
|
|
|
|
return astgen.addInst(.{ .texture_load = .{
|
|
.kind = a1_inst.texture_type.kind,
|
|
.texture = a1,
|
|
.coords = a2,
|
|
.level = a3,
|
|
.result_type = result_type,
|
|
} });
|
|
}
|
|
|
|
fn genTextureStoreBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len < 3) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, arg_nodes.len);
|
|
}
|
|
|
|
const a1_node = arg_nodes[0];
|
|
const a1_node_loc = astgen.tree.nodeLoc(a1_node);
|
|
const a1 = try astgen.genExpr(scope, a1_node);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a1_inst = astgen.getInst(a1_res);
|
|
|
|
const a2_node = arg_nodes[1];
|
|
const a2_node_loc = astgen.tree.nodeLoc(a2_node);
|
|
const a2 = try astgen.genExpr(scope, a2_node);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a2_inst = astgen.getInst(a2_res);
|
|
|
|
const a3_node = arg_nodes[2];
|
|
const a3_node_loc = astgen.tree.nodeLoc(a3_node);
|
|
const a3 = try astgen.genExpr(scope, a3_node);
|
|
const a3_res = try astgen.resolve(a3);
|
|
const a3_inst = astgen.getInst(a3_res);
|
|
|
|
if (a1_inst != .texture_type) {
|
|
try astgen.errors.add(a1_node_loc, "expected a texture type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a2_inst != .vector or a2_inst.vector.size != .two or astgen.getInst(a2_inst.vector.elem_type) != .int) {
|
|
try astgen.errors.add(a2_node_loc, "expected vec2<i32> or vec2<u32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a3_inst != .vector or a3_inst.vector.size != .four) {
|
|
try astgen.errors.add(a3_node_loc, "expected vec4", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.storage_2d => {},
|
|
else => {
|
|
try astgen.errors.add(a1_node_loc, "invalid texture", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
return astgen.addInst(.{ .texture_store = .{
|
|
.kind = a1_inst.texture_type.kind,
|
|
.texture = a1,
|
|
.coords = a2,
|
|
.value = a3,
|
|
} });
|
|
}
|
|
|
|
fn genSimpleBuiltin(astgen: *AstGen, comptime op: Air.Inst.NilIntrinsic) !InstIndex {
|
|
return astgen.addInst(.{ .nil_intrinsic = op });
|
|
}
|
|
|
|
fn genTextureSampleBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len < 3) {
|
|
return astgen.failArgCountMismatch(node_loc, 3, arg_nodes.len);
|
|
}
|
|
|
|
const a1_node = arg_nodes[0];
|
|
const a1_node_loc = astgen.tree.nodeLoc(a1_node);
|
|
const a1 = try astgen.genExpr(scope, a1_node);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a1_inst = astgen.getInst(a1_res);
|
|
|
|
const a2_node = arg_nodes[1];
|
|
const a2_node_loc = astgen.tree.nodeLoc(a2_node);
|
|
const a2 = try astgen.genExpr(scope, a2_node);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a2_inst = astgen.getInst(a2_res);
|
|
|
|
const a3_node = arg_nodes[2];
|
|
const a3_node_loc = astgen.tree.nodeLoc(a3_node);
|
|
const a3 = try astgen.genExpr(scope, a3_node);
|
|
const a3_res = try astgen.resolve(a3);
|
|
const a3_inst = astgen.getInst(a3_res);
|
|
|
|
if (a1_inst != .texture_type) {
|
|
// TODO: depth textures
|
|
try astgen.errors.add(a1_node_loc, "expected a texture type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a2_inst != .sampler_type) {
|
|
try astgen.errors.add(a2_node_loc, "expected a sampler", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_1d => {
|
|
if (a3_inst != .float) {
|
|
try astgen.errors.add(a3_node_loc, "expected a f32", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
.sampled_2d, .sampled_2d_array, .depth_2d, .depth_2d_array => {
|
|
if (a3_inst != .vector or a3_inst.vector.size != .two) {
|
|
try astgen.errors.add(a3_node_loc, "expected a vec2<f32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
.sampled_3d, .sampled_cube, .sampled_cube_array, .depth_cube, .depth_cube_array => {
|
|
if (a3_inst != .vector or a3_inst.vector.size != .three) {
|
|
try astgen.errors.add(a3_node_loc, "expected a vec3<f32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
else => {
|
|
try astgen.errors.add(a3_node_loc, "invalid texture", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
var offset = InstIndex.none;
|
|
var array_index = InstIndex.none;
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_2d, .sampled_3d, .sampled_cube, .depth_2d, .depth_cube => {
|
|
if (arg_nodes.len == 4) {
|
|
const a4_node = arg_nodes[3];
|
|
const a4_node_loc = astgen.tree.nodeLoc(a4_node);
|
|
const a4 = try astgen.genExpr(scope, a4_node);
|
|
const a4_res = try astgen.resolve(a4);
|
|
const a4_inst = astgen.getInst(a4_res);
|
|
offset = a4;
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_3d, .sampled_cube, .depth_cube => {
|
|
if (a4_inst != .vector or a4_inst.vector.size != .three) {
|
|
try astgen.errors.add(a4_node_loc, "expected a vec3<i32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
.sampled_2d, .depth_2d => if (a4_inst != .vector or a4_inst.vector.size != .two) {
|
|
try astgen.errors.add(a4_node_loc, "expected a vec2<i32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
},
|
|
.sampled_2d_array, .sampled_cube_array, .depth_2d_array, .depth_cube_array => {
|
|
if (arg_nodes.len < 4) {
|
|
return astgen.failArgCountMismatch(node_loc, 4, arg_nodes.len);
|
|
}
|
|
|
|
const a4_node = arg_nodes[3];
|
|
const a4_node_loc = astgen.tree.nodeLoc(a4_node);
|
|
const a4 = try astgen.genExpr(scope, a4_node);
|
|
const a4_res = try astgen.resolve(a4);
|
|
const a4_inst = astgen.getInst(a4_res);
|
|
array_index = a4;
|
|
|
|
if (a4_inst != .int) {
|
|
try astgen.errors.add(a4_node_loc, "expected i32 or u32", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (arg_nodes.len == 5) {
|
|
const a5_node = arg_nodes[5];
|
|
const a5_node_loc = astgen.tree.nodeLoc(a5_node);
|
|
const a5 = try astgen.genExpr(scope, a5_node);
|
|
const a5_res = try astgen.resolve(a5);
|
|
const a5_inst = astgen.getInst(a5_res);
|
|
offset = a5;
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_cube_array, .depth_cube_array => {
|
|
if (a5_inst != .vector or a5_inst.vector.size != .three) {
|
|
try astgen.errors.add(a5_node_loc, "expected a vec3<i32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
.sampled_2d_array, .depth_2d_array => {
|
|
if (a5_inst != .vector or a5_inst.vector.size != .two) {
|
|
try astgen.errors.add(a5_node_loc, "expected a vec2<i32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
const result_type = switch (a1_inst.texture_type.kind) {
|
|
.depth_2d,
|
|
.depth_2d_array,
|
|
.depth_cube,
|
|
.depth_cube_array,
|
|
=> try astgen.addInst(.{ .float = .{ .type = .f32, .value = null } }),
|
|
else => try astgen.addInst(.{ .vector = .{
|
|
.elem_type = try astgen.addInst(.{ .float = .{ .type = .f32, .value = null } }),
|
|
.size = .four,
|
|
.value = null,
|
|
} }),
|
|
};
|
|
return astgen.addInst(.{ .texture_sample = .{
|
|
.kind = a1_inst.texture_type.kind,
|
|
.texture_type = a1_res,
|
|
.texture = a1,
|
|
.sampler = a2,
|
|
.coords = a3,
|
|
.offset = offset,
|
|
.array_index = array_index,
|
|
.result_type = result_type,
|
|
} });
|
|
}
|
|
|
|
// TODO: Partial implementation
|
|
fn genTextureSampleLevelBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 4, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len < 4) {
|
|
return astgen.failArgCountMismatch(node_loc, 4, arg_nodes.len);
|
|
}
|
|
|
|
const a1_node = arg_nodes[0];
|
|
const a1_node_loc = astgen.tree.nodeLoc(a1_node);
|
|
const a1 = try astgen.genExpr(scope, a1_node);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a1_inst = astgen.getInst(a1_res);
|
|
|
|
const a2_node = arg_nodes[1];
|
|
const a2_node_loc = astgen.tree.nodeLoc(a2_node);
|
|
const a2 = try astgen.genExpr(scope, a2_node);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a2_inst = astgen.getInst(a2_res);
|
|
|
|
const a3_node = arg_nodes[2];
|
|
const a3_node_loc = astgen.tree.nodeLoc(a3_node);
|
|
const a3 = try astgen.genExpr(scope, a3_node);
|
|
const a3_res = try astgen.resolve(a3);
|
|
const a3_inst = astgen.getInst(a3_res);
|
|
|
|
const a4_node = arg_nodes[3];
|
|
const a4_node_loc = astgen.tree.nodeLoc(a4_node);
|
|
const a4 = try astgen.genExpr(scope, a4_node);
|
|
const a4_res = try astgen.resolve(a4);
|
|
const a4_inst = astgen.getInst(a4_res);
|
|
|
|
if (a1_inst != .texture_type) {
|
|
try astgen.errors.add(a1_node_loc, "expected a texture type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_2d => {},
|
|
else => {
|
|
try astgen.errors.add(a1_node_loc, "invalid texture", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
if (a2_inst != .sampler_type) {
|
|
try astgen.errors.add(a2_node_loc, "expected a sampler", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a3_inst != .vector or a3_inst.vector.size != .two) {
|
|
try astgen.errors.add(a3_node_loc, "expected a vec2<f32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a4_inst != .float) {
|
|
try astgen.errors.add(a4_node_loc, "expected f32", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const result_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = try astgen.addInst(.{ .float = .{ .type = .f32, .value = null } }),
|
|
.size = .four,
|
|
.value = null,
|
|
} });
|
|
return astgen.addInst(.{ .texture_sample = .{
|
|
.kind = a1_inst.texture_type.kind,
|
|
.texture_type = a1_res,
|
|
.texture = a1,
|
|
.sampler = a2,
|
|
.coords = a3,
|
|
.result_type = result_type,
|
|
.operands = .{ .level = a4 },
|
|
} });
|
|
}
|
|
|
|
fn genTextureSampleGradBuiltin(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
if (node_lhs == .none) {
|
|
return astgen.failArgCountMismatch(node_loc, 4, 0);
|
|
}
|
|
|
|
const arg_nodes = astgen.tree.spanToList(node_lhs);
|
|
if (arg_nodes.len < 4) {
|
|
return astgen.failArgCountMismatch(node_loc, 4, arg_nodes.len);
|
|
}
|
|
|
|
const a1_node = arg_nodes[0];
|
|
const a1_node_loc = astgen.tree.nodeLoc(a1_node);
|
|
const a1 = try astgen.genExpr(scope, a1_node);
|
|
const a1_res = try astgen.resolve(a1);
|
|
const a1_inst = astgen.getInst(a1_res);
|
|
|
|
const a2_node = arg_nodes[1];
|
|
const a2_node_loc = astgen.tree.nodeLoc(a2_node);
|
|
const a2 = try astgen.genExpr(scope, a2_node);
|
|
const a2_res = try astgen.resolve(a2);
|
|
const a2_inst = astgen.getInst(a2_res);
|
|
|
|
const a3_node = arg_nodes[2];
|
|
const a3_node_loc = astgen.tree.nodeLoc(a3_node);
|
|
const a3 = try astgen.genExpr(scope, a3_node);
|
|
const a3_res = try astgen.resolve(a3);
|
|
const a3_inst = astgen.getInst(a3_res);
|
|
|
|
const a4_node = arg_nodes[3];
|
|
const a4_node_loc = astgen.tree.nodeLoc(a4_node);
|
|
const a4 = try astgen.genExpr(scope, a4_node);
|
|
const a4_res = try astgen.resolve(a4);
|
|
const a4_inst = astgen.getInst(a4_res);
|
|
|
|
const a5_node = arg_nodes[3];
|
|
const a5_node_loc = astgen.tree.nodeLoc(a5_node);
|
|
const a5 = try astgen.genExpr(scope, a5_node);
|
|
const a5_res = try astgen.resolve(a5);
|
|
const a5_inst = astgen.getInst(a5_res);
|
|
|
|
if (a1_inst != .texture_type) {
|
|
try astgen.errors.add(a1_node_loc, "expected a texture type", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
switch (a1_inst.texture_type.kind) {
|
|
.sampled_2d => {},
|
|
else => {
|
|
try astgen.errors.add(a1_node_loc, "invalid texture", .{}, null);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
if (a2_inst != .sampler_type) {
|
|
try astgen.errors.add(a2_node_loc, "expected a sampler", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a3_inst != .vector or
|
|
astgen.getInst(a4_inst.vector.elem_type) != .float or
|
|
a3_inst.vector.size != .two)
|
|
{
|
|
try astgen.errors.add(a3_node_loc, "expected a vec2<f32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a4_inst != .vector or
|
|
astgen.getInst(a4_inst.vector.elem_type) != .float or
|
|
a4_inst.vector.size != .two)
|
|
{
|
|
try astgen.errors.add(a4_node_loc, "expected vec2<f32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (a5_inst != .vector or
|
|
astgen.getInst(a5_inst.vector.elem_type) != .float or
|
|
a5_inst.vector.size != .two)
|
|
{
|
|
try astgen.errors.add(a5_node_loc, "expected vec2<f32>", .{}, null);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const result_type = try astgen.addInst(.{ .vector = .{
|
|
.elem_type = try astgen.addInst(.{ .float = .{ .type = .f32, .value = null } }),
|
|
.size = .four,
|
|
.value = null,
|
|
} });
|
|
return astgen.addInst(.{ .texture_sample = .{
|
|
.kind = a1_inst.texture_type.kind,
|
|
.texture_type = a1_res,
|
|
.texture = a1,
|
|
.sampler = a2,
|
|
.coords = a3,
|
|
.result_type = result_type,
|
|
.operands = .{ .grad = .{ .dpdx = a4, .dpdy = a5 } },
|
|
} });
|
|
}
|
|
|
|
fn genVarRef(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const inst_idx = try astgen.findSymbol(scope, astgen.tree.nodeToken(node));
|
|
switch (astgen.getInst(inst_idx)) {
|
|
.@"var" => |inst| {
|
|
if (inst.addr_space != .function) {
|
|
try astgen.global_var_refs.put(astgen.allocator, inst_idx, {});
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
return astgen.addInst(.{ .var_ref = inst_idx });
|
|
}
|
|
|
|
fn genIndexAccess(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const base = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
const base_type = try astgen.resolve(base);
|
|
|
|
switch (astgen.getInst(base_type)) {
|
|
.vector, .matrix, .array => {},
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(astgen.tree.nodeRHS(node)),
|
|
"cannot access index of a non-array",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
|
|
const rhs = try astgen.genExpr(scope, astgen.tree.nodeRHS(node));
|
|
const rhs_res = try astgen.resolve(rhs);
|
|
const elem_type = switch (astgen.getInst(base_type)) {
|
|
inline .vector, .array => |ty| ty.elem_type,
|
|
.matrix => |ty| try astgen.addInst(.{ .vector = .{
|
|
.elem_type = ty.elem_type,
|
|
.size = ty.rows,
|
|
.value = null,
|
|
} }),
|
|
else => unreachable,
|
|
};
|
|
|
|
if (astgen.getInst(rhs_res) == .int) {
|
|
const inst = try astgen.addInst(.{
|
|
.index_access = .{
|
|
.base = base,
|
|
.type = elem_type,
|
|
.index = rhs,
|
|
},
|
|
});
|
|
return inst;
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(astgen.tree.nodeRHS(node)),
|
|
"index must be an integer",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genFieldAccess(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const base = try astgen.genExpr(scope, astgen.tree.nodeLHS(node));
|
|
const base_type = try astgen.resolve(base);
|
|
const field_node = astgen.tree.nodeRHS(node).asTokenIndex();
|
|
const field_name = astgen.tree.tokenLoc(field_node).slice(astgen.tree.source);
|
|
|
|
switch (astgen.getInst(base_type)) {
|
|
.vector => |base_vec| {
|
|
if (field_name.len > 4) {
|
|
try astgen.errors.add(
|
|
astgen.tree.tokenLoc(field_node),
|
|
"invalid swizzle name",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
var pattern: [4]Inst.SwizzleAccess.Component = undefined;
|
|
for (field_name, 0..) |c, i| {
|
|
pattern[i] = switch (c) {
|
|
'x', 'r' => .x,
|
|
'y', 'g' => .y,
|
|
'z', 'b' => .z,
|
|
'w', 'a' => .w,
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.tokenLoc(field_node),
|
|
"invalid swizzle name",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
};
|
|
}
|
|
|
|
const inst = try astgen.addInst(.{
|
|
.swizzle_access = .{
|
|
.base = base,
|
|
.type = base_vec.elem_type,
|
|
.size = @enumFromInt(field_name.len),
|
|
.pattern = pattern,
|
|
},
|
|
});
|
|
return inst;
|
|
},
|
|
.@"struct" => |@"struct"| {
|
|
const struct_members = @"struct".members;
|
|
for (astgen.refToList(struct_members)) |member| {
|
|
const member_data = astgen.getInst(member).struct_member;
|
|
if (std.mem.eql(u8, field_name, astgen.getStr(member_data.name))) {
|
|
if (astgen.current_fn_scope.tag.@"fn".flattened_params.get(member)) |fv| {
|
|
return try astgen.addInst(.{ .var_ref = fv });
|
|
}
|
|
|
|
const inst = try astgen.addInst(.{
|
|
.field_access = .{
|
|
.base = base,
|
|
.field = member,
|
|
.name = member_data.name,
|
|
},
|
|
});
|
|
return inst;
|
|
}
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node),
|
|
"struct '{s}' has no member named '{s}'",
|
|
.{
|
|
astgen.getStr(@"struct".name),
|
|
field_name,
|
|
},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node),
|
|
"expected struct type",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn genType(astgen: *AstGen, scope: *Scope, node: NodeIndex) error{ AnalysisFail, OutOfMemory }!InstIndex {
|
|
const inst = switch (astgen.tree.nodeTag(node)) {
|
|
.bool_type => try astgen.addInst(.{ .bool = .{ .value = null } }),
|
|
.number_type => try astgen.genNumberType(node),
|
|
.vector_type => try astgen.genVectorType(scope, node),
|
|
.matrix_type => try astgen.genMatrixType(scope, node),
|
|
.atomic_type => try astgen.genAtomicType(scope, node),
|
|
.array_type => try astgen.genArray(scope, node, null),
|
|
.ptr_type => try astgen.genPtrType(scope, node),
|
|
.sampler_type => try astgen.genSamplerType(node),
|
|
.sampled_texture_type => try astgen.genSampledTextureType(scope, node),
|
|
.multisampled_texture_type => try astgen.genMultisampledTextureType(scope, node),
|
|
.storage_texture_type => try astgen.genStorageTextureType(node),
|
|
.depth_texture_type => try astgen.genDepthTextureType(node),
|
|
.external_texture_type => try astgen.addInst(.external_texture_type),
|
|
.ident => {
|
|
const node_loc = astgen.tree.nodeLoc(node);
|
|
const decl = try astgen.findSymbol(scope, astgen.tree.nodeToken(node));
|
|
switch (astgen.getInst(decl)) {
|
|
.bool,
|
|
.int,
|
|
.float,
|
|
.vector,
|
|
.matrix,
|
|
.atomic_type,
|
|
.array,
|
|
.ptr_type,
|
|
.sampler_type,
|
|
.comparison_sampler_type,
|
|
.external_texture_type,
|
|
.texture_type,
|
|
.@"struct",
|
|
=> return decl,
|
|
else => {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"'{s}' is not a type",
|
|
.{node_loc.slice(astgen.tree.source)},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
},
|
|
else => unreachable,
|
|
};
|
|
return inst;
|
|
}
|
|
|
|
fn genNumberType(astgen: *AstGen, node: NodeIndex) !InstIndex {
|
|
const token = astgen.tree.nodeToken(node);
|
|
const token_tag = astgen.tree.tokenTag(token);
|
|
return astgen.addInst(switch (token_tag) {
|
|
.k_u32 => .{ .int = .{ .type = .u32, .value = null } },
|
|
.k_i32 => .{ .int = .{ .type = .i32, .value = null } },
|
|
.k_f32 => .{ .float = .{ .type = .f32, .value = null } },
|
|
.k_f16 => .{ .float = .{ .type = .f16, .value = null } },
|
|
else => unreachable,
|
|
});
|
|
}
|
|
|
|
fn genVectorType(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const elem_type_node = astgen.tree.nodeLHS(node);
|
|
const elem_type = try astgen.genType(scope, elem_type_node);
|
|
|
|
switch (astgen.getInst(elem_type)) {
|
|
.bool, .int, .float => {
|
|
const token_tag = astgen.tree.tokenTag(astgen.tree.nodeToken(node));
|
|
return astgen.addInst(.{
|
|
.vector = .{
|
|
.size = switch (token_tag) {
|
|
.k_vec2 => .two,
|
|
.k_vec3 => .three,
|
|
.k_vec4 => .four,
|
|
else => unreachable,
|
|
},
|
|
.elem_type = elem_type,
|
|
.value = null,
|
|
},
|
|
});
|
|
},
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(elem_type_node),
|
|
"invalid vector component type",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"must be 'i32', 'u32', 'f32', 'f16' or 'bool'",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn genMatrixType(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const elem_type_node = astgen.tree.nodeLHS(node);
|
|
const elem_type = try astgen.genType(scope, elem_type_node);
|
|
|
|
switch (astgen.getInst(elem_type)) {
|
|
.bool, .int, .float => {
|
|
const token_tag = astgen.tree.tokenTag(astgen.tree.nodeToken(node));
|
|
return astgen.addInst(.{
|
|
.matrix = .{
|
|
.cols = matrixCols(token_tag),
|
|
.rows = matrixRows(token_tag),
|
|
.elem_type = elem_type,
|
|
.value = null,
|
|
},
|
|
});
|
|
},
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(elem_type_node),
|
|
"invalid matrix component type",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"must be 'f32', or 'f16'",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn matrixCols(tag: TokenTag) Air.Inst.Vector.Size {
|
|
return switch (tag) {
|
|
.k_mat2x2, .k_mat2x3, .k_mat2x4 => .two,
|
|
.k_mat3x2, .k_mat3x3, .k_mat3x4 => .three,
|
|
.k_mat4x2, .k_mat4x3, .k_mat4x4 => .four,
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn matrixRows(tag: TokenTag) Air.Inst.Vector.Size {
|
|
return switch (tag) {
|
|
.k_mat2x2, .k_mat3x2, .k_mat4x2 => .two,
|
|
.k_mat2x3, .k_mat3x3, .k_mat4x3 => .three,
|
|
.k_mat2x4, .k_mat3x4, .k_mat4x4 => .four,
|
|
else => unreachable,
|
|
};
|
|
}
|
|
|
|
fn genAtomicType(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const elem_type = try astgen.genType(scope, node_lhs);
|
|
|
|
if (astgen.getInst(elem_type) == .int) {
|
|
return astgen.addInst(.{ .atomic_type = .{ .elem_type = elem_type } });
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node_lhs),
|
|
"invalid atomic component type",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"must be 'i32' or 'u32'",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genPtrType(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const elem_type = try astgen.genType(scope, node_lhs);
|
|
|
|
switch (astgen.getInst(elem_type)) {
|
|
.bool,
|
|
.int,
|
|
.float,
|
|
.sampler_type,
|
|
.comparison_sampler_type,
|
|
.external_texture_type,
|
|
=> {
|
|
const extra = astgen.tree.extraData(Node.PtrType, astgen.tree.nodeRHS(node));
|
|
|
|
const addr_space_loc = astgen.tree.tokenLoc(extra.addr_space);
|
|
const ast_addr_space = stringToEnum(Ast.AddressSpace, addr_space_loc.slice(astgen.tree.source)).?;
|
|
const addr_space: Inst.PointerType.AddressSpace = switch (ast_addr_space) {
|
|
.function => .function,
|
|
.private => .private,
|
|
.workgroup => .workgroup,
|
|
.uniform => .uniform,
|
|
.storage => .storage,
|
|
};
|
|
|
|
const access_mode_loc = astgen.tree.tokenLoc(extra.access_mode);
|
|
const ast_access_mode = stringToEnum(Ast.AccessMode, access_mode_loc.slice(astgen.tree.source)).?;
|
|
const access_mode: Inst.PointerType.AccessMode = switch (ast_access_mode) {
|
|
.read => .read,
|
|
.write => .write,
|
|
.read_write => .read_write,
|
|
};
|
|
|
|
return astgen.addInst(.{
|
|
.ptr_type = .{
|
|
.elem_type = elem_type,
|
|
.addr_space = addr_space,
|
|
.access_mode = access_mode,
|
|
},
|
|
});
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node_lhs),
|
|
"invalid pointer component type",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genArray(astgen: *AstGen, scope: *Scope, node: NodeIndex, args: ?RefIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
var elem_type = InstIndex.none;
|
|
if (node_lhs != .none) {
|
|
elem_type = try astgen.genType(scope, node_lhs);
|
|
switch (astgen.getInst(elem_type)) {
|
|
.array,
|
|
.atomic_type,
|
|
.@"struct",
|
|
.bool,
|
|
.int,
|
|
.float,
|
|
.vector,
|
|
.matrix,
|
|
=> {
|
|
if (astgen.getInst(elem_type) == .array) {
|
|
if (astgen.getInst(elem_type).array.len == .none) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node_lhs),
|
|
"array component type can not be a runtime-known array",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
},
|
|
else => {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node_lhs),
|
|
"invalid array component type",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
}
|
|
}
|
|
|
|
if (args != null) {
|
|
if (args.? == .none) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node),
|
|
"element type not specified",
|
|
.{},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
if (elem_type == .none) {
|
|
elem_type = astgen.refToList(args.?)[0];
|
|
}
|
|
}
|
|
|
|
const len_node = astgen.tree.nodeRHS(node);
|
|
var len = InstIndex.none;
|
|
if (len_node != .none) {
|
|
len = try astgen.genExpr(scope, len_node);
|
|
} else if (args != null) {
|
|
len = try astgen.addInst(.{ .int = .{
|
|
.type = .u32,
|
|
.value = try astgen.addValue(Inst.Int.Value, .{ .literal = @intCast(astgen.refToList(args.?).len) }),
|
|
} });
|
|
}
|
|
|
|
return astgen.addInst(.{
|
|
.array = .{
|
|
.elem_type = elem_type,
|
|
.len = len,
|
|
.value = args,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genSamplerType(astgen: *AstGen, node: NodeIndex) !InstIndex {
|
|
const token = astgen.tree.nodeToken(node);
|
|
const token_tag = astgen.tree.tokenTag(token);
|
|
return astgen.addInst(switch (token_tag) {
|
|
.k_sampler => .sampler_type,
|
|
.k_sampler_comparison => .comparison_sampler_type,
|
|
else => unreachable,
|
|
});
|
|
}
|
|
|
|
fn genSampledTextureType(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const elem_type = try astgen.genType(scope, node_lhs);
|
|
const elem_type_inst = astgen.getInst(elem_type);
|
|
|
|
if (elem_type_inst == .int or (elem_type_inst == .float and elem_type_inst.float.type == .f32)) {
|
|
const token_tag = astgen.tree.tokenTag(astgen.tree.nodeToken(node));
|
|
return astgen.addInst(.{
|
|
.texture_type = .{
|
|
.kind = switch (token_tag) {
|
|
.k_texture_1d => .sampled_1d,
|
|
.k_texture_2d => .sampled_2d,
|
|
.k_texture_2d_array => .sampled_2d_array,
|
|
.k_texture_3d => .sampled_3d,
|
|
.k_texture_cube => .sampled_cube,
|
|
.k_texture_cube_array => .sampled_cube_array,
|
|
else => unreachable,
|
|
},
|
|
.elem_type = elem_type,
|
|
},
|
|
});
|
|
}
|
|
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node_lhs),
|
|
"invalid texture component type",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"must be 'i32', 'u32' or 'f32'",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn genMultisampledTextureType(astgen: *AstGen, scope: *Scope, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
var elem_type = InstIndex.none;
|
|
|
|
if (node_lhs != .none) {
|
|
elem_type = try astgen.genType(scope, node_lhs);
|
|
const elem_type_inst = astgen.getInst(elem_type);
|
|
|
|
if (elem_type_inst != .int and !(elem_type_inst == .float and elem_type_inst.float.type == .f32)) {
|
|
try astgen.errors.add(
|
|
astgen.tree.nodeLoc(node_lhs),
|
|
"invalid multisampled texture component type",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"must be 'i32', 'u32' or 'f32'",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
}
|
|
|
|
const token_tag = astgen.tree.tokenTag(astgen.tree.nodeToken(node));
|
|
return astgen.addInst(.{
|
|
.texture_type = .{
|
|
.kind = switch (token_tag) {
|
|
.k_texture_multisampled_2d => .multisampled_2d,
|
|
.k_texture_depth_multisampled_2d => .multisampled_depth_2d,
|
|
else => unreachable,
|
|
},
|
|
.elem_type = elem_type,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn genStorageTextureType(astgen: *AstGen, node: NodeIndex) !InstIndex {
|
|
const node_lhs = astgen.tree.nodeLHS(node);
|
|
const texel_format_loc = astgen.tree.tokenLoc(node_lhs.asTokenIndex());
|
|
const ast_texel_format = stringToEnum(Ast.TexelFormat, texel_format_loc.slice(astgen.tree.source)).?;
|
|
const texel_format: Inst.TextureType.TexelFormat = switch (ast_texel_format) {
|
|
.rgba8unorm => .rgba8unorm,
|
|
.rgba8snorm => .rgba8snorm,
|
|
.rgba8uint => .rgba8uint,
|
|
.rgba8sint => .rgba8sint,
|
|
.rgba16uint => .rgba16uint,
|
|
.rgba16sint => .rgba16sint,
|
|
.rgba16float => .rgba16float,
|
|
.r32uint => .r32uint,
|
|
.r32sint => .r32sint,
|
|
.r32float => .r32float,
|
|
.rg32uint => .rg32uint,
|
|
.rg32sint => .rg32sint,
|
|
.rg32float => .rg32float,
|
|
.rgba32uint => .rgba32uint,
|
|
.rgba32sint => .rgba32sint,
|
|
.rgba32float => .rgba32float,
|
|
.bgra8unorm => .bgra8unorm,
|
|
};
|
|
|
|
const node_rhs = astgen.tree.nodeRHS(node);
|
|
const access_mode_loc = astgen.tree.tokenLoc(node_rhs.asTokenIndex());
|
|
const access_mode_full = stringToEnum(Ast.AccessMode, access_mode_loc.slice(astgen.tree.source)).?;
|
|
const access_mode = switch (access_mode_full) {
|
|
.write => Inst.TextureType.AccessMode.write,
|
|
else => {
|
|
try astgen.errors.add(
|
|
access_mode_loc,
|
|
"invalid access mode",
|
|
.{},
|
|
try astgen.errors.createNote(
|
|
null,
|
|
"only 'write' is allowed",
|
|
.{},
|
|
),
|
|
);
|
|
return error.AnalysisFail;
|
|
},
|
|
};
|
|
|
|
const token_tag = astgen.tree.tokenTag(astgen.tree.nodeToken(node));
|
|
const inst = try astgen.addInst(.{
|
|
.texture_type = .{
|
|
.kind = switch (token_tag) {
|
|
.k_texture_storage_1d => .storage_1d,
|
|
.k_texture_storage_2d => .storage_2d,
|
|
.k_texture_storage_2d_array => .storage_2d_array,
|
|
.k_texture_storage_3d => .storage_3d,
|
|
else => unreachable,
|
|
},
|
|
.texel_format = texel_format,
|
|
.access_mode = access_mode,
|
|
},
|
|
});
|
|
|
|
return inst;
|
|
}
|
|
|
|
fn genDepthTextureType(astgen: *AstGen, node: NodeIndex) !InstIndex {
|
|
const token_tag = astgen.tree.tokenTag(astgen.tree.nodeToken(node));
|
|
const inst = try astgen.addInst(.{
|
|
.texture_type = .{ .kind = switch (token_tag) {
|
|
.k_texture_depth_2d => .depth_2d,
|
|
.k_texture_depth_2d_array => .depth_2d_array,
|
|
.k_texture_depth_cube => .depth_cube,
|
|
.k_texture_depth_cube_array => .depth_cube_array,
|
|
else => unreachable,
|
|
} },
|
|
});
|
|
return inst;
|
|
}
|
|
|
|
/// takes token and returns the first declaration in the current and parent scopes
|
|
fn findSymbol(astgen: *AstGen, scope: *Scope, token: TokenIndex) error{ OutOfMemory, AnalysisFail }!InstIndex {
|
|
std.debug.assert(astgen.tree.tokenTag(token) == .ident);
|
|
|
|
const loc = astgen.tree.tokenLoc(token);
|
|
const name = loc.slice(astgen.tree.source);
|
|
|
|
var s = scope;
|
|
while (true) {
|
|
var iter = s.decls.iterator();
|
|
while (iter.next()) |decl| {
|
|
const decl_node = decl.key_ptr.*;
|
|
const decl_inst = try decl.value_ptr.*;
|
|
if (std.mem.eql(u8, name, astgen.tree.declNameLoc(decl_node).?.slice(astgen.tree.source))) {
|
|
if (decl_inst == .none) {
|
|
// declaration has not analysed
|
|
switch (s.tag) {
|
|
.root => return astgen.genGlobalDecl(s, decl_node),
|
|
.@"fn",
|
|
.block,
|
|
.loop,
|
|
.continuing,
|
|
.switch_case,
|
|
.@"if",
|
|
.@"for",
|
|
=> {},
|
|
}
|
|
} else {
|
|
return decl_inst;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s.tag == .root) {
|
|
try astgen.errors.add(
|
|
loc,
|
|
"use of undeclared identifier '{s}'",
|
|
.{name},
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
s = s.parent;
|
|
}
|
|
}
|
|
|
|
fn resolve(astgen: *AstGen, index: InstIndex) !InstIndex {
|
|
var idx = index;
|
|
|
|
while (true) {
|
|
const inst = astgen.getInst(idx);
|
|
switch (inst) {
|
|
inline .bool, .int, .float, .vector, .matrix, .array => |data| {
|
|
std.debug.assert(data.value != null);
|
|
return idx;
|
|
},
|
|
.struct_construct => |struct_construct| return struct_construct.@"struct",
|
|
.select => |select| return select.type,
|
|
inline .texture_sample,
|
|
.bitcast,
|
|
.unary,
|
|
.unary_intrinsic,
|
|
.binary,
|
|
.binary_intrinsic,
|
|
.triple_intrinsic,
|
|
.texture_dimension,
|
|
.texture_load,
|
|
=> |instruction| return instruction.result_type,
|
|
|
|
.call => |call| return astgen.getInst(call.@"fn").@"fn".return_type,
|
|
.var_ref => |var_ref| idx = var_ref,
|
|
|
|
.field_access => |field_access| return astgen.getInst(field_access.field).struct_member.type,
|
|
.swizzle_access => |swizzle_access| {
|
|
if (swizzle_access.size == .one) {
|
|
return swizzle_access.type;
|
|
}
|
|
return astgen.addInst(.{
|
|
.vector = .{
|
|
.elem_type = swizzle_access.type,
|
|
.size = @enumFromInt(@intFromEnum(swizzle_access.size)),
|
|
.value = null,
|
|
},
|
|
});
|
|
},
|
|
.index_access => |index_access| return index_access.type,
|
|
|
|
inline .@"var", .@"const" => |decl| {
|
|
std.debug.assert(index != idx);
|
|
|
|
const decl_type = decl.type;
|
|
const decl_expr = decl.init;
|
|
if (decl_type != .none) return decl_type;
|
|
idx = decl_expr;
|
|
},
|
|
|
|
.fn_param => |param| return param.type,
|
|
|
|
.nil_intrinsic,
|
|
.texture_store,
|
|
.atomic_type,
|
|
.ptr_type,
|
|
.sampler_type,
|
|
.comparison_sampler_type,
|
|
.external_texture_type,
|
|
.texture_type,
|
|
.@"fn",
|
|
.@"struct",
|
|
.struct_member,
|
|
.block,
|
|
.loop,
|
|
.continuing,
|
|
.@"return",
|
|
.break_if,
|
|
.@"if",
|
|
.@"while",
|
|
.@"for",
|
|
.discard,
|
|
.@"break",
|
|
.@"continue",
|
|
.@"switch",
|
|
.switch_case,
|
|
.assign,
|
|
=> unreachable,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn eql(astgen: *AstGen, a_idx: InstIndex, b_idx: InstIndex) bool {
|
|
const a = astgen.getInst(a_idx);
|
|
const b = astgen.getInst(b_idx);
|
|
|
|
return switch (a) {
|
|
.int => |int_a| switch (b) {
|
|
.int => |int_b| int_a.type == int_b.type,
|
|
else => false,
|
|
},
|
|
.vector => |vec_a| switch (b) {
|
|
.vector => |vec_b| astgen.eqlVector(vec_a, vec_b),
|
|
else => false,
|
|
},
|
|
.matrix => |mat_a| switch (b) {
|
|
.matrix => |mat_b| astgen.eqlMatrix(mat_a, mat_b),
|
|
else => false,
|
|
},
|
|
else => if (std.meta.activeTag(a) == std.meta.activeTag(b)) true else false,
|
|
};
|
|
}
|
|
|
|
fn eqlVector(astgen: *AstGen, a: Air.Inst.Vector, b: Air.Inst.Vector) bool {
|
|
return a.size == b.size and astgen.eql(a.elem_type, b.elem_type);
|
|
}
|
|
|
|
fn eqlMatrix(astgen: *AstGen, a: Air.Inst.Matrix, b: Air.Inst.Matrix) bool {
|
|
return a.cols == b.cols and a.rows == b.rows and astgen.eql(a.elem_type, b.elem_type);
|
|
}
|
|
|
|
fn addInst(astgen: *AstGen, inst: Inst) error{OutOfMemory}!InstIndex {
|
|
try astgen.instructions.put(astgen.allocator, inst, {});
|
|
return @enumFromInt(astgen.instructions.getIndex(inst).?);
|
|
}
|
|
|
|
fn addRefList(astgen: *AstGen, list: []const InstIndex) error{OutOfMemory}!RefIndex {
|
|
const len = list.len + 1;
|
|
try astgen.refs.ensureUnusedCapacity(astgen.allocator, len);
|
|
astgen.refs.appendSliceAssumeCapacity(list);
|
|
astgen.refs.appendAssumeCapacity(.none);
|
|
return @as(RefIndex, @enumFromInt(astgen.refs.items.len - len));
|
|
}
|
|
|
|
fn addString(astgen: *AstGen, str: []const u8) error{OutOfMemory}!StringIndex {
|
|
const len = str.len + 1;
|
|
try astgen.strings.ensureUnusedCapacity(astgen.allocator, len);
|
|
astgen.strings.appendSliceAssumeCapacity(str);
|
|
astgen.strings.appendAssumeCapacity(0);
|
|
return @enumFromInt(astgen.strings.items.len - len);
|
|
}
|
|
|
|
fn addValue(astgen: *AstGen, comptime T: type, value: T) error{OutOfMemory}!ValueIndex {
|
|
const value_bytes = std.mem.asBytes(&value);
|
|
try astgen.values.appendSlice(astgen.allocator, value_bytes);
|
|
std.testing.expectEqual(value, std.mem.bytesToValue(T, value_bytes)) catch unreachable;
|
|
return @enumFromInt(astgen.values.items.len - value_bytes.len);
|
|
}
|
|
|
|
fn getInst(astgen: *AstGen, inst: InstIndex) Inst {
|
|
return astgen.instructions.entries.slice().items(.key)[@intFromEnum(inst)];
|
|
}
|
|
|
|
fn getValue(astgen: *AstGen, comptime T: type, value: ValueIndex) T {
|
|
return std.mem.bytesAsValue(T, astgen.values.items[@intFromEnum(value)..][0..@sizeOf(T)]).*;
|
|
}
|
|
|
|
fn getStr(astgen: *AstGen, index: StringIndex) []const u8 {
|
|
return std.mem.sliceTo(astgen.strings.items[@intFromEnum(index)..], 0);
|
|
}
|
|
|
|
fn refToList(astgen: *AstGen, ref: RefIndex) []const InstIndex {
|
|
return std.mem.sliceTo(astgen.refs.items[@intFromEnum(ref)..], .none);
|
|
}
|
|
|
|
fn failArgCountMismatch(
|
|
astgen: *AstGen,
|
|
node_loc: Loc,
|
|
expected: usize,
|
|
actual: usize,
|
|
) error{ OutOfMemory, AnalysisFail } {
|
|
try astgen.errors.add(
|
|
node_loc,
|
|
"expected {} argument(s), found {}",
|
|
.{ expected, actual },
|
|
null,
|
|
);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
const BuiltinFn = enum {
|
|
all,
|
|
any,
|
|
select,
|
|
arrayLength,
|
|
abs,
|
|
acos,
|
|
acosh,
|
|
asin,
|
|
asinh,
|
|
atan,
|
|
atanh,
|
|
atan2,
|
|
ceil,
|
|
clamp,
|
|
cos,
|
|
cosh,
|
|
countLeadingZeros,
|
|
countOneBits,
|
|
countTrailingZeros,
|
|
cross, // unimplemented
|
|
degrees,
|
|
determinant, // unimplemented
|
|
distance,
|
|
dot, // unimplemented
|
|
exp,
|
|
exp2,
|
|
extractBits, // unimplemented
|
|
faceForward, // unimplemented
|
|
firstLeadingBit,
|
|
firstTrailingBit,
|
|
floor,
|
|
fma, // unimplemented
|
|
fract,
|
|
frexp, // unimplemented
|
|
insertBits, // unimplemented
|
|
inverseSqrt,
|
|
ldexp, // unimplemented
|
|
length,
|
|
log,
|
|
log2,
|
|
max,
|
|
min,
|
|
mix,
|
|
modf, // unimplemented
|
|
normalize,
|
|
pow, // unimplemented
|
|
quantizeToF16,
|
|
radians,
|
|
reflect, // unimplemented
|
|
refract, // unimplemented
|
|
reverseBits,
|
|
round,
|
|
saturate,
|
|
sign,
|
|
sin,
|
|
sinh,
|
|
smoothstep,
|
|
sqrt,
|
|
step,
|
|
tan,
|
|
tanh,
|
|
transpose, // unimplemented
|
|
trunc,
|
|
dpdx,
|
|
dpdxCoarse,
|
|
dpdxFine,
|
|
dpdy,
|
|
dpdyCoarse,
|
|
dpdyFine,
|
|
fwidth,
|
|
fwidthCoarse,
|
|
fwidthFine,
|
|
textureDimensions,
|
|
textureGather, // unimplemented
|
|
textureLoad,
|
|
textureNumLayers, // unimplemented
|
|
textureNumLevels, // unimplemented
|
|
textureNumSamples, // unimplemented
|
|
textureSample,
|
|
textureSampleBias, // unimplemented
|
|
textureSampleCompare, // unimplemented
|
|
textureSampleCompareLevel, // unimplemented
|
|
textureSampleGrad, // unimplemented
|
|
textureSampleLevel, // unimplemented
|
|
textureSampleBaseClampToEdge, // unimplemented
|
|
textureStore, // unimplemented
|
|
atomicLoad, // unimplemented
|
|
atomicStore, // unimplemented
|
|
atomicAdd, // unimplemented
|
|
atomicSub, // unimplemented
|
|
atomicMax, // unimplemented
|
|
atomicMin, // unimplemented
|
|
atomicAnd, // unimplemented
|
|
atomicOr, // unimplemented
|
|
atomicXor, // unimplemented
|
|
atomicExchange, // unimplemented
|
|
atomicCompareExchangeWeak, // unimplemented
|
|
pack4x8unorm, // unimplemented
|
|
pack2x16snorm, // unimplemented
|
|
pack2x16unorm, // unimplemented
|
|
pack2x16float, // unimplemented
|
|
unpack4x8snorm, // unimplemented
|
|
unpack4x8unorm, // unimplemented
|
|
unpack2x16snorm, // unimplemented
|
|
unpack2x16unorm, // unimplemented
|
|
unpack2x16float, // unimplemented
|
|
storageBarrier,
|
|
workgroupBarrier,
|
|
workgroupUniformLoad, // unimplemented
|
|
};
|