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 {...@@ -417,22 +417,24 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
417 .f64 => return .f64_neg,417 .f64 => return .f64_neg,
418 },418 },
419 .ceil => switch (args.valtype1.?) {419 .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.
421 .f32 => return .f32_ceil,422 .f32 => return .f32_ceil,
422 .f64 => return .f64_ceil,423 .f64 => return .f64_ceil,
423 },424 },
424 .floor => switch (args.valtype1.?) {425 .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.
426 .f32 => return .f32_floor,428 .f32 => return .f32_floor,
427 .f64 => return .f64_floor,429 .f64 => return .f64_floor,
428 },430 },
429 .trunc => switch (args.valtype1.?) {431 .trunc => switch (args.valtype1.?) {
430 .i32 => switch (args.valtype2.?) {432 .i32 => if (args.valtype2) |valty| switch (valty) {
431 .i32 => unreachable,433 .i32 => unreachable,
432 .i64 => unreachable,434 .i64 => unreachable,
433 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,435 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
434 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,436 .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.
436 .i64 => unreachable,438 .i64 => unreachable,
437 .f32 => return .f32_trunc,439 .f32 => return .f32_trunc,
438 .f64 => return .f64_trunc,440 .f64 => return .f64_trunc,
...@@ -788,55 +790,53 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {...@@ -788,55 +790,53 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
788790
789/// Generates a `wasm.Type` from a given function type.791/// Generates a `wasm.Type` from a given function type.
790/// Memory is owned by the caller.792/// Memory is owned by the caller.
791fn genFunctype(gpa: Allocator, fn_info: Type.Payload.Function.Data, target: std.Target) !wasm.Type {793fn genFunctype(gpa: Allocator, cc: std.builtin.CallingConvention, params: []const Type, return_type: Type, target: std.Target) !wasm.Type {
792 var params = std.ArrayList(wasm.Valtype).init(gpa);794 var temp_params = std.ArrayList(wasm.Valtype).init(gpa);
793 defer params.deinit();795 defer temp_params.deinit();
794 var returns = std.ArrayList(wasm.Valtype).init(gpa);796 var returns = std.ArrayList(wasm.Valtype).init(gpa);
795 defer returns.deinit();797 defer returns.deinit();
796798
797 if (firstParamSRet(fn_info.cc, fn_info.return_type, target)) {799 if (firstParamSRet(cc, return_type, target)) {
798 try params.append(.i32); // memory address is always a 32-bit handle800 try temp_params.append(.i32); // memory address is always a 32-bit handle
799 } else if (fn_info.return_type.hasRuntimeBitsIgnoreComptime()) {801 } else if (return_type.hasRuntimeBitsIgnoreComptime()) {
800 if (fn_info.cc == .C) {802 if (cc == .C) {
801 const res_classes = abi.classifyType(fn_info.return_type, target);803 const res_classes = abi.classifyType(return_type, target);
802 assert(res_classes[0] == .direct and res_classes[1] == .none);804 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);
804 try returns.append(typeToValtype(scalar_type, target));806 try returns.append(typeToValtype(scalar_type, target));
805 } else {807 } else {
806 try returns.append(typeToValtype(fn_info.return_type, target));808 try returns.append(typeToValtype(return_type, target));
807 }809 }
808 } else if (fn_info.return_type.isError()) {810 } else if (return_type.isError()) {
809 try returns.append(.i32);811 try returns.append(.i32);
810 }812 }
811813
812 // param types814 // param types
813 if (fn_info.param_types.len != 0) {815 for (params) |param_type| {
814 for (fn_info.param_types) |param_type| {816 if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;
815 if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;817
816818 switch (cc) {
817 switch (fn_info.cc) {819 .C => {
818 .C => {820 const param_classes = abi.classifyType(param_type, target);
819 const param_classes = abi.classifyType(param_type, target);821 for (param_classes) |class| {
820 for (param_classes) |class| {822 if (class == .none) continue;
821 if (class == .none) continue;823 if (class == .direct) {
822 if (class == .direct) {824 const scalar_type = abi.scalarType(param_type, target);
823 const scalar_type = abi.scalarType(param_type, target);825 try temp_params.append(typeToValtype(scalar_type, target));
824 try params.append(typeToValtype(scalar_type, target));826 } else {
825 } else {827 try temp_params.append(typeToValtype(param_type, target));
826 try params.append(typeToValtype(param_type, target));
827 }
828 }828 }
829 },829 }
830 else => if (isByRef(param_type, target))830 },
831 try params.append(.i32)831 else => if (isByRef(param_type, target))
832 else832 try temp_params.append(.i32)
833 try params.append(typeToValtype(param_type, target)),833 else
834 }834 try temp_params.append(typeToValtype(param_type, target)),
835 }835 }
836 }836 }
837837
838 return wasm.Type{838 return wasm.Type{
839 .params = params.toOwnedSlice(),839 .params = temp_params.toOwnedSlice(),
840 .returns = returns.toOwnedSlice(),840 .returns = returns.toOwnedSlice(),
841 };841 };
842}842}
...@@ -877,7 +877,8 @@ pub fn generate(...@@ -877,7 +877,8 @@ pub fn generate(
877}877}
878878
879fn genFunc(self: *Self) InnerError!void {879fn 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);
881 defer func_type.deinit(self.gpa);882 defer func_type.deinit(self.gpa);
882 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);883 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....@@ -1733,7 +1734,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1733 break :blk module.declPtr(func.data.owner_decl);1734 break :blk module.declPtr(func.data.owner_decl);
1734 } else if (func_val.castTag(.extern_fn)) |extern_fn| {1735 } else if (func_val.castTag(.extern_fn)) |extern_fn| {
1735 const ext_decl = module.declPtr(extern_fn.data.owner_decl);1736 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);
1737 defer func_type.deinit(self.gpa);1739 defer func_type.deinit(self.gpa);
1738 ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);1740 ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
1739 try self.bin_file.addOrUpdateImport(1741 try self.bin_file.addOrUpdateImport(
...@@ -1774,7 +1776,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions....@@ -1774,7 +1776,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1774 const operand = try self.resolveInst(pl_op.operand);1776 const operand = try self.resolveInst(pl_op.operand);
1775 try self.emitWValue(operand);1777 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);
1778 defer fn_type.deinit(self.gpa);1780 defer fn_type.deinit(self.gpa);
17791781
1780 const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);1782 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 {...@@ -4883,12 +4885,38 @@ fn airDivFloor(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
4883 try self.emitWValue(rem_result);4885 try self.emitWValue(rem_result);
4884 try self.addTag(.select);4886 try self.addTag(.select);
4885 } else {4887 } else {
4886 const div_result = try self.binOp(lhs, rhs, ty, .div);4888 const float_bits = ty.floatBits(self.target);
4887 try self.emitWValue(div_result);4889 if (float_bits > 64) {
4888 switch (ty.floatBits(self.target)) {4890 return self.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{float_bits});
4889 32 => try self.addTag(.f32_floor),4891 }
4890 64 => try self.addTag(.f64_floor),4892 const is_f16 = float_bits == 16;
4891 else => |bit_size| return self.fail("TODO: `@divFloor` for floats with bitsize: {d}", .{bit_size}),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);
4892 }4920 }
4893 }4921 }
48944922
...@@ -4961,22 +4989,28 @@ fn airCeilFloorTrunc(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValu...@@ -4961,22 +4989,28 @@ fn airCeilFloorTrunc(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValu
49614989
4962 const un_op = self.air.instructions.items(.data)[inst].un_op;4990 const un_op = self.air.instructions.items(.data)[inst].un_op;
4963 const ty = self.air.typeOfIndex(inst);4991 const ty = self.air.typeOfIndex(inst);
4992 const float_bits = ty.floatBits(self.target);
4993 const is_f16 = float_bits == 16;
49644994
4965 if (ty.zigTypeTag() == .Vector) {4995 if (ty.zigTypeTag() == .Vector) {
4966 return self.fail("TODO: Implement `@ceil` for vectors", .{});4996 return self.fail("TODO: Implement `@ceil` for vectors", .{});
4967 }4997 }
4998 if (float_bits > 64) {
4999 return self.fail("TODO: implement `@ceil`, `@trunc`, `@floor` for floats larger than 64bits", .{});
5000 }
49685001
4969 const operand = try self.resolveInst(un_op);5002 const operand = try self.resolveInst(un_op);
4970 try self.emitWValue(operand);5003 const op_to_lower = if (is_f16) blk: {
4971 switch (ty.floatBits(self.target)) {5004 break :blk try self.fpext(operand, Type.f16, Type.f32);
4972 32, 64 => {5005 } else operand;
4973 const opcode = buildOpcode(.{5006 try self.emitWValue(op_to_lower);
4974 .op = op,5007 const opcode = buildOpcode(.{ .op = op, .valtype1 = typeToValtype(ty, self.target) });
4975 .valtype1 = typeToValtype(ty, self.target),5008 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
4976 });5009
4977 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));5010 if (is_f16) {
4978 },5011 // re-use temporary to save locals
4979 else => |bit_size| return self.fail("TODO: Implement `@ceil` for floats with bitsize {d}", .{bit_size}),5012 try self.addLabel(.local_set, op_to_lower.local);
5013 return self.fptrunc(op_to_lower, Type.f32, Type.f16);
4980 }5014 }
49815015
4982 const result = try self.allocLocal(ty);5016 const result = try self.allocLocal(ty);
...@@ -5212,19 +5246,8 @@ fn callIntrinsic(...@@ -5212,19 +5246,8 @@ fn callIntrinsic(
5212 return self.fail("Could not find or create global symbol '{s}'", .{@errorName(err)});5246 return self.fail("Could not find or create global symbol '{s}'", .{@errorName(err)});
5213 };5247 };
52145248
5215 // TODO: have genFunctype accept individual params so we don't,5249 // Always pass over C-ABI
5216 // need to initialize a fake Fn.Data instance.5250 var func_type = try genFunctype(self.gpa, .C, param_types, return_type, self.target);
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);
5228 defer func_type.deinit(self.gpa);5251 defer func_type.deinit(self.gpa);
5229 const func_type_index = try self.bin_file.putOrGetFuncType(func_type);5252 const func_type_index = try self.bin_file.putOrGetFuncType(func_type);
5230 try self.bin_file.addOrUpdateImport(name, symbol_index, null, func_type_index);5253 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...@@ -1840,7 +1840,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod
1840 try positionals.append(c_object.status.success.object_path);1840 try positionals.append(c_object.status.success.object_path);
1841 }1841 }
18421842
1843 if (comp.compiler_rt_static_lib) |lib| {1843 if (comp.compiler_rt_lib) |lib| {
1844 try positionals.append(lib.full_object_path);1844 try positionals.append(lib.full_object_path);
1845 }1845 }
18461846