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 matCxR 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 matCxK 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 matKxR 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 // bitcast(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(vec2) -> 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>(T) -> vec2 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) -> vecN result_type = lhs; } else { // bitcast>(vecN) -> 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>(vec4) -> vec2 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>(vec2) -> vec4 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 or vec2", .{}, 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 or vec2", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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", .{}, 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 };