authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-06-13 20:48:53+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-06-24 08:12:17+02:00
log6ae898b244aba909f802340907f0bdcfa2ed9b33
treea4cd0cc8f8c67e34fa4f2f96997dac187abde095
parentba37bc81e96a9318229193a8e7d000cb88d58cb6

wasm: more f16 support and cleanup of intrinsics

`genFunctype` now accepts calling convention, param types, and return type as part of its function signature rather than `fnData`. This means we no longer have to create a dummy for our intrinsic call abstraction. This also adds support for f16 division and builtins such as `@ceil` & more.

2 files changed, 93 insertions(+), 70 deletions(-)

src/arch/wasm/CodeGen.zig+92-69
......@@ -417,22 +417,24 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
417417 .f64 => return .f64_neg,
418418 },
419419 .ceil => switch (args.valtype1.?) {
420 .i32, .i64 => unreachable,
420 .i64 => unreachable,
421 .i32 => return .f32_ceil, // when valtype is f16, we store it in i32.
421422 .f32 => return .f32_ceil,
422423 .f64 => return .f64_ceil,
423424 },
424425 .floor => switch (args.valtype1.?) {
425 .i32, .i64 => unreachable,
426 .i64 => unreachable,
427 .i32 => return .f32_floor, // when valtype is f16, we store it in i32.
426428 .f32 => return .f32_floor,
427429 .f64 => return .f64_floor,
428430 },
429431 .trunc => switch (args.valtype1.?) {
430 .i32 => switch (args.valtype2.?) {
432 .i32 => if (args.valtype2) |valty| switch (valty) {
431433 .i32 => unreachable,
432434 .i64 => unreachable,
433435 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
434436 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
435 },
437 } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32.
436438 .i64 => unreachable,
437439 .f32 => return .f32_trunc,
438440 .f64 => return .f64_trunc,
......@@ -788,55 +790,53 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
788790
789791/// Generates a `wasm.Type` from a given function type.
790792/// Memory is owned by the caller.
791fn genFunctype(gpa: Allocator, fn_info: Type.Payload.Function.Data, target: std.Target) !wasm.Type {
792 var params = std.ArrayList(wasm.Valtype).init(gpa);
793 defer params.deinit();
793fn genFunctype(gpa: Allocator, cc: std.builtin.CallingConvention, params: []const Type, return_type: Type, target: std.Target) !wasm.Type {
794 var temp_params = std.ArrayList(wasm.Valtype).init(gpa);
795 defer temp_params.deinit();
794796 var returns = std.ArrayList(wasm.Valtype).init(gpa);
795797 defer returns.deinit();
796798
797 if (firstParamSRet(fn_info.cc, fn_info.return_type, target)) {
798 try params.append(.i32); // memory address is always a 32-bit handle
799 } else if (fn_info.return_type.hasRuntimeBitsIgnoreComptime()) {
800 if (fn_info.cc == .C) {
801 const res_classes = abi.classifyType(fn_info.return_type, target);
799 if (firstParamSRet(cc, return_type, target)) {
800 try temp_params.append(.i32); // memory address is always a 32-bit handle
801 } else if (return_type.hasRuntimeBitsIgnoreComptime()) {
802 if (cc == .C) {
803 const res_classes = abi.classifyType(return_type, target);
802804 assert(res_classes[0] == .direct and res_classes[1] == .none);
803 const scalar_type = abi.scalarType(fn_info.return_type, target);
805 const scalar_type = abi.scalarType(return_type, target);
804806 try returns.append(typeToValtype(scalar_type, target));
805807 } else {
806 try returns.append(typeToValtype(fn_info.return_type, target));
808 try returns.append(typeToValtype(return_type, target));
807809 }
808 } else if (fn_info.return_type.isError()) {
810 } else if (return_type.isError()) {
809811 try returns.append(.i32);
810812 }
811813
812814 // param types
813 if (fn_info.param_types.len != 0) {
814 for (fn_info.param_types) |param_type| {
815 if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;
816
817 switch (fn_info.cc) {
818 .C => {
819 const param_classes = abi.classifyType(param_type, target);
820 for (param_classes) |class| {
821 if (class == .none) continue;
822 if (class == .direct) {
823 const scalar_type = abi.scalarType(param_type, target);
824 try params.append(typeToValtype(scalar_type, target));
825 } else {
826 try params.append(typeToValtype(param_type, target));
827 }
815 for (params) |param_type| {
816 if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;
817
818 switch (cc) {
819 .C => {
820 const param_classes = abi.classifyType(param_type, target);
821 for (param_classes) |class| {
822 if (class == .none) continue;
823 if (class == .direct) {
824 const scalar_type = abi.scalarType(param_type, target);
825 try temp_params.append(typeToValtype(scalar_type, target));
826 } else {
827 try temp_params.append(typeToValtype(param_type, target));
828828 }
829 },
830 else => if (isByRef(param_type, target))
831 try params.append(.i32)
832 else
833 try params.append(typeToValtype(param_type, target)),
834 }
829 }
830 },
831 else => if (isByRef(param_type, target))
832 try temp_params.append(.i32)
833 else
834 try temp_params.append(typeToValtype(param_type, target)),
835835 }
836836 }
837837
838838 return wasm.Type{
839 .params = params.toOwnedSlice(),
839 .params = temp_params.toOwnedSlice(),
840840 .returns = returns.toOwnedSlice(),
841841 };
842842}
......@@ -877,7 +877,8 @@ pub fn generate(
877877}
878878
879879fn genFunc(self: *Self) InnerError!void {
880 var func_type = try genFunctype(self.gpa, self.decl.ty.fnInfo(), self.target);
880 const fn_info = self.decl.ty.fnInfo();
881 var func_type = try genFunctype(self.gpa, fn_info.cc, fn_info.param_types, fn_info.return_type, self.target);
881882 defer func_type.deinit(self.gpa);
882883 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
883884
......@@ -1733,7 +1734,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
17331734 break :blk module.declPtr(func.data.owner_decl);
17341735 } else if (func_val.castTag(.extern_fn)) |extern_fn| {
17351736 const ext_decl = module.declPtr(extern_fn.data.owner_decl);
1736 var func_type = try genFunctype(self.gpa, ext_decl.ty.fnInfo(), self.target);
1737 const ext_info = ext_decl.ty.fnInfo();
1738 var func_type = try genFunctype(self.gpa, ext_info.cc, ext_info.param_types, ext_info.return_type, self.target);
17371739 defer func_type.deinit(self.gpa);
17381740 ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
17391741 try self.bin_file.addOrUpdateImport(
......@@ -1774,7 +1776,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
17741776 const operand = try self.resolveInst(pl_op.operand);
17751777 try self.emitWValue(operand);
17761778
1777 var fn_type = try genFunctype(self.gpa, fn_ty.fnInfo(), self.target);
1779 var fn_type = try genFunctype(self.gpa, fn_info.cc, fn_info.param_types, fn_info.return_type, self.target);
17781780 defer fn_type.deinit(self.gpa);
17791781
17801782 const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);
......@@ -4883,12 +4885,38 @@ fn airDivFloor(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
48834885 try self.emitWValue(rem_result);
48844886 try self.addTag(.select);
48854887 } else {
4886 const div_result = try self.binOp(lhs, rhs, ty, .div);
4887 try self.emitWValue(div_result);
4888 switch (ty.floatBits(self.target)) {
4889 32 => try self.addTag(.f32_floor),
4890 64 => try self.addTag(.f64_floor),
4891 else => |bit_size| return self.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{bit_size}),
4888 const float_bits = ty.floatBits(self.target);
4889 if (float_bits > 64) {
4890 return self.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{float_bits});
4891 }
4892 const is_f16 = float_bits == 16;
4893
4894 const lhs_operand = if (is_f16) blk: {
4895 break :blk try self.fpext(lhs, Type.f16, Type.f32);
4896 } else lhs;
4897 const rhs_operand = if (is_f16) blk: {
4898 break :blk try self.fpext(rhs, Type.f16, Type.f32);
4899 } else rhs;
4900
4901 try self.emitWValue(lhs_operand);
4902 try self.emitWValue(rhs_operand);
4903
4904 switch (float_bits) {
4905 16, 32 => {
4906 try self.addTag(.f32_div);
4907 try self.addTag(.f32_floor);
4908 },
4909 64 => {
4910 try self.addTag(.f64_div);
4911 try self.addTag(.f64_floor);
4912 },
4913 else => unreachable,
4914 }
4915
4916 if (is_f16) {
4917 // we can re-use temporary local
4918 try self.addLabel(.local_set, lhs_operand.local);
4919 return self.fptrunc(lhs_operand, Type.f32, Type.f16);
48924920 }
48934921 }
48944922
......@@ -4961,22 +4989,28 @@ fn airCeilFloorTrunc(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValu
49614989
49624990 const un_op = self.air.instructions.items(.data)[inst].un_op;
49634991 const ty = self.air.typeOfIndex(inst);
4992 const float_bits = ty.floatBits(self.target);
4993 const is_f16 = float_bits == 16;
49644994
49654995 if (ty.zigTypeTag() == .Vector) {
49664996 return self.fail("TODO: Implement `@ceil` for vectors", .{});
49674997 }
4998 if (float_bits > 64) {
4999 return self.fail("TODO: implement `@ceil`, `@trunc`, `@floor` for floats larger than 64bits", .{});
5000 }
49685001
49695002 const operand = try self.resolveInst(un_op);
4970 try self.emitWValue(operand);
4971 switch (ty.floatBits(self.target)) {
4972 32, 64 => {
4973 const opcode = buildOpcode(.{
4974 .op = op,
4975 .valtype1 = typeToValtype(ty, self.target),
4976 });
4977 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
4978 },
4979 else => |bit_size| return self.fail("TODO: Implement `@ceil` for floats with bitsize {d}", .{bit_size}),
5003 const op_to_lower = if (is_f16) blk: {
5004 break :blk try self.fpext(operand, Type.f16, Type.f32);
5005 } else operand;
5006 try self.emitWValue(op_to_lower);
5007 const opcode = buildOpcode(.{ .op = op, .valtype1 = typeToValtype(ty, self.target) });
5008 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
5009
5010 if (is_f16) {
5011 // re-use temporary to save locals
5012 try self.addLabel(.local_set, op_to_lower.local);
5013 return self.fptrunc(op_to_lower, Type.f32, Type.f16);
49805014 }
49815015
49825016 const result = try self.allocLocal(ty);
......@@ -5212,19 +5246,8 @@ fn callIntrinsic(
52125246 return self.fail("Could not find or create global symbol '{s}'", .{@errorName(err)});
52135247 };
52145248
5215 // TODO: have genFunctype accept individual params so we don't,
5216 // need to initialize a fake Fn.Data instance.
5217 var pt_tmp = try self.gpa.dupe(Type, param_types);
5218 defer self.gpa.free(pt_tmp);
5219 var func_type = try genFunctype(self.gpa, .{
5220 .param_types = pt_tmp,
5221 .comptime_params = undefined,
5222 .return_type = return_type,
5223 .alignment = 0,
5224 .cc = .C,
5225 .is_var_args = false,
5226 .is_generic = false,
5227 }, self.target);
5249 // Always pass over C-ABI
5250 var func_type = try genFunctype(self.gpa, .C, param_types, return_type, self.target);
52285251 defer func_type.deinit(self.gpa);
52295252 const func_type_index = try self.bin_file.putOrGetFuncType(func_type);
52305253 try self.bin_file.addOrUpdateImport(name, symbol_index, null, func_type_index);
src/link/Wasm.zig+1-1
......@@ -1840,7 +1840,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
18401840 try positionals.append(c_object.status.success.object_path);
18411841 }
18421842
1843 if (comp.compiler_rt_static_lib) |lib| {
1843 if (comp.compiler_rt_lib) |lib| {
18441844 try positionals.append(lib.full_object_path);
18451845 }
18461846