| ... | @@ -915,7 +915,7 @@ fn genTypedValue(self: *Self, ty: Type, val: Value) InnerError!Result { | ... | @@ -915,7 +915,7 @@ fn genTypedValue(self: *Self, ty: Type, val: Value) InnerError!Result { |
| 915 | }, | 915 | }, |
| 916 | .array => { | 916 | .array => { |
| 917 | const elem_vals = val.castTag(.array).?.data; | 917 | const elem_vals = val.castTag(.array).?.data; |
| 918 | const elem_ty = ty.elemType(); | 918 | const elem_ty = ty.childType(); |
| 919 | for (elem_vals) |elem_val| { | 919 | for (elem_vals) |elem_val| { |
| 920 | switch (try self.genTypedValue(elem_ty, elem_val)) { | 920 | switch (try self.genTypedValue(elem_ty, elem_val)) { |
| 921 | .appended => {}, | 921 | .appended => {}, |
| ... | @@ -1269,10 +1269,15 @@ fn isByRef(ty: Type) bool { | ... | @@ -1269,10 +1269,15 @@ fn isByRef(ty: Type) bool { |
| 1269 | | 1269 | |
| 1270 | /// Creates a new local for a pointer that points to memory with given offset. | 1270 | /// Creates a new local for a pointer that points to memory with given offset. |
| 1271 | /// This can be used to get a pointer to a struct field, error payload, etc. | 1271 | /// This can be used to get a pointer to a struct field, error payload, etc. |
| 1272 | fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64) InnerError!WValue { | 1272 | /// By providing `modify` as action, it will modify the given `ptr_value` instead of making a new |
| | 1273 | /// local value to store the pointer. This allows for local re-use and improves binary size. |
| | 1274 | fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64, action: enum { modify, new }) InnerError!WValue { |
| 1273 | // do not perform arithmetic when offset is 0. | 1275 | // do not perform arithmetic when offset is 0. |
| 1274 | if (offset == 0) return ptr_value; | 1276 | if (offset == 0) return ptr_value; |
| 1275 | const result_ptr = try self.allocLocal(Type.usize); | 1277 | const result_ptr: WValue = switch (action) { |
| | 1278 | .new => try self.allocLocal(Type.usize), |
| | 1279 | .modify => ptr_value, |
| | 1280 | }; |
| 1276 | try self.emitWValue(ptr_value); | 1281 | try self.emitWValue(ptr_value); |
| 1277 | switch (self.target.cpu.arch.ptrBitWidth()) { | 1282 | switch (self.target.cpu.arch.ptrBitWidth()) { |
| 1278 | 32 => { | 1283 | 32 => { |
| ... | @@ -1289,6 +1294,16 @@ fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64) InnerError!WV | ... | @@ -1289,6 +1294,16 @@ fn buildPointerOffset(self: *Self, ptr_value: WValue, offset: u64) InnerError!WV |
| 1289 | return result_ptr; | 1294 | return result_ptr; |
| 1290 | } | 1295 | } |
| 1291 | | 1296 | |
| | 1297 | /// Creates a new local and sets its value to the given `value` local. |
| | 1298 | /// User must ensure `ty` matches that of given `value`. |
| | 1299 | /// Asserts `value` is a `local`. |
| | 1300 | fn copyLocal(self: *Self, value: WValue, ty: Type) InnerError!WValue { |
| | 1301 | const copy = try self.allocLocal(ty); |
| | 1302 | try self.addLabel(.local_get, value.local); |
| | 1303 | try self.addLabel(.local_set, copy.local); |
| | 1304 | return copy; |
| | 1305 | } |
| | 1306 | |
| 1292 | fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { | 1307 | fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1293 | const air_tags = self.air.instructions.items(.tag); | 1308 | const air_tags = self.air.instructions.items(.tag); |
| 1294 | return switch (air_tags[inst]) { | 1309 | return switch (air_tags[inst]) { |
| ... | @@ -1312,6 +1327,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { | ... | @@ -1312,6 +1327,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1312 | .cmp_lt => self.airCmp(inst, .lt), | 1327 | .cmp_lt => self.airCmp(inst, .lt), |
| 1313 | .cmp_neq => self.airCmp(inst, .neq), | 1328 | .cmp_neq => self.airCmp(inst, .neq), |
| 1314 | | 1329 | |
| | 1330 | .array_elem_val => self.airArrayElemVal(inst), |
| 1315 | .array_to_slice => self.airArrayToSlice(inst), | 1331 | .array_to_slice => self.airArrayToSlice(inst), |
| 1316 | .alloc => self.airAlloc(inst), | 1332 | .alloc => self.airAlloc(inst), |
| 1317 | .arg => self.airArg(inst), | 1333 | .arg => self.airArg(inst), |
| ... | @@ -1601,8 +1617,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro | ... | @@ -1601,8 +1617,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro |
| 1601 | const tag_local = try self.load(rhs, tag_ty, 0); | 1617 | const tag_local = try self.load(rhs, tag_ty, 0); |
| 1602 | if (payload_ty.hasCodeGenBits()) { | 1618 | if (payload_ty.hasCodeGenBits()) { |
| 1603 | if (isByRef(payload_ty)) { | 1619 | if (isByRef(payload_ty)) { |
| 1604 | const payload_ptr = try self.buildPointerOffset(rhs, payload_offset); | 1620 | const payload_ptr = try self.buildPointerOffset(rhs, payload_offset, .new); |
| 1605 | const lhs_payload_ptr = try self.buildPointerOffset(lhs, payload_offset); | 1621 | const lhs_payload_ptr = try self.buildPointerOffset(lhs, payload_offset, .new); |
| 1606 | try self.store(lhs_payload_ptr, payload_ptr, payload_ty, 0); | 1622 | try self.store(lhs_payload_ptr, payload_ptr, payload_ty, 0); |
| 1607 | } else { | 1623 | } else { |
| 1608 | const payload_local = try self.load(rhs, payload_ty, payload_offset); | 1624 | const payload_local = try self.load(rhs, payload_ty, payload_offset); |
| ... | @@ -1632,7 +1648,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro | ... | @@ -1632,7 +1648,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro |
| 1632 | else => unreachable, | 1648 | else => unreachable, |
| 1633 | } | 1649 | } |
| 1634 | }, | 1650 | }, |
| 1635 | .Struct => { | 1651 | .Struct, .Array => { |
| 1636 | if (rhs == .constant) { | 1652 | if (rhs == .constant) { |
| 1637 | try self.emitWValue(rhs); | 1653 | try self.emitWValue(rhs); |
| 1638 | try self.addLabel(.local_set, lhs.local); | 1654 | try self.addLabel(.local_set, lhs.local); |
| ... | @@ -1968,19 +1984,76 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { | ... | @@ -1968,19 +1984,76 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| 1968 | const result = try self.allocStack(ty); | 1984 | const result = try self.allocStack(ty); |
| 1969 | | 1985 | |
| 1970 | const fields = ty.structFields(); | 1986 | const fields = ty.structFields(); |
| 1971 | var offset: u32 = 0; | 1987 | const offset = try self.copyLocal(result, ty); |
| 1972 | for (fields.values()) |field, index| { | 1988 | for (fields.values()) |field, index| { |
| 1973 | if (isByRef(field.ty)) { | | |
| 1974 | return self.fail("TODO: emitConstant for struct field type {}\n", .{field.ty}); | | |
| 1975 | } | | |
| 1976 | const tmp = try self.allocLocal(field.ty); | 1989 | const tmp = try self.allocLocal(field.ty); |
| 1977 | try self.emitConstant(struct_data.data[index], field.ty); | 1990 | try self.emitConstant(struct_data.data[index], field.ty); |
| 1978 | try self.addLabel(.local_set, tmp.local); | 1991 | try self.addLabel(.local_set, tmp.local); |
| 1979 | try self.store(result, tmp, field.ty, offset); | 1992 | try self.store(offset, tmp, field.ty, 0); |
| 1980 | offset += @intCast(u32, field.ty.abiSize(self.target)); | 1993 | |
| | 1994 | // this prevents us from emitting useless instructions when we reached the end of the loop |
| | 1995 | if (index != (fields.count() - 1)) { |
| | 1996 | _ = try self.buildPointerOffset(offset, field.ty.abiSize(self.target), .modify); |
| | 1997 | } |
| 1981 | } | 1998 | } |
| 1982 | try self.addLabel(.local_get, result.local); | 1999 | try self.addLabel(.local_get, result.local); |
| 1983 | }, | 2000 | }, |
| | 2001 | .Array => { |
| | 2002 | const result = try self.allocStack(ty); |
| | 2003 | if (val.castTag(.bytes)) |bytes| { |
| | 2004 | for (bytes.data) |byte, index| { |
| | 2005 | try self.addLabel(.local_get, result.local); |
| | 2006 | try self.addImm32(@intCast(i32, byte)); |
| | 2007 | try self.addMemArg(.i32_store8, .{ .offset = @intCast(u32, index), .alignment = 1 }); |
| | 2008 | } |
| | 2009 | } else if (val.castTag(.array)) |array| { |
| | 2010 | const elem_ty = ty.childType(); |
| | 2011 | const elem_size = elem_ty.abiSize(self.target); |
| | 2012 | const tmp = try self.allocLocal(elem_ty); |
| | 2013 | const offset = try self.copyLocal(result, ty); |
| | 2014 | for (array.data) |value, index| { |
| | 2015 | try self.emitConstant(value, elem_ty); |
| | 2016 | try self.addLabel(.local_set, tmp.local); |
| | 2017 | try self.store(offset, tmp, elem_ty, 0); |
| | 2018 | |
| | 2019 | if (index != (array.data.len - 1)) { |
| | 2020 | _ = try self.buildPointerOffset(offset, elem_size, .modify); |
| | 2021 | } |
| | 2022 | } |
| | 2023 | } else if (val.castTag(.repeated)) |repeated| { |
| | 2024 | const value = repeated.data; |
| | 2025 | const elem_ty = ty.childType(); |
| | 2026 | const elem_size = elem_ty.abiSize(self.target); |
| | 2027 | const sentinel = ty.sentinel(); |
| | 2028 | const len = ty.arrayLen(); |
| | 2029 | const len_with_sent = len + @boolToInt(sentinel != null); |
| | 2030 | const tmp = try self.allocLocal(elem_ty); |
| | 2031 | const offset = try self.copyLocal(result, ty); |
| | 2032 | |
| | 2033 | var index: u32 = 0; |
| | 2034 | while (index < len_with_sent) : (index += 1) { |
| | 2035 | if (sentinel != null and index == len) { |
| | 2036 | try self.emitConstant(sentinel.?, elem_ty); |
| | 2037 | } else { |
| | 2038 | try self.emitConstant(value, elem_ty); |
| | 2039 | } |
| | 2040 | try self.addLabel(.local_set, tmp.local); |
| | 2041 | try self.store(offset, tmp, elem_ty, 0); |
| | 2042 | |
| | 2043 | if (index != (len_with_sent - 1)) { |
| | 2044 | _ = try self.buildPointerOffset(offset, elem_size, .modify); |
| | 2045 | } |
| | 2046 | } |
| | 2047 | } else if (val.tag() == .empty_array_sentinel) { |
| | 2048 | const elem_ty = ty.childType(); |
| | 2049 | const sent_val = ty.sentinel().?; |
| | 2050 | const tmp = try self.allocLocal(elem_ty); |
| | 2051 | try self.emitConstant(sent_val, elem_ty); |
| | 2052 | try self.addLabel(.local_set, tmp.local); |
| | 2053 | try self.store(result, tmp, elem_ty, 0); |
| | 2054 | } else unreachable; |
| | 2055 | try self.addLabel(.local_get, result.local); |
| | 2056 | }, |
| 1984 | else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), | 2057 | else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), |
| 1985 | } | 2058 | } |
| 1986 | } | 2059 | } |
| ... | @@ -2010,12 +2083,19 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void { | ... | @@ -2010,12 +2083,19 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void { |
| 2010 | 33...64 => try self.addFloat64(@bitCast(f64, @as(u64, 0xaaaaaaaaaaaaaaaa))), | 2083 | 33...64 => try self.addFloat64(@bitCast(f64, @as(u64, 0xaaaaaaaaaaaaaaaa))), |
| 2011 | else => |bits| return self.fail("Wasm TODO: emitUndefined for float bitsize: {d}", .{bits}), | 2084 | else => |bits| return self.fail("Wasm TODO: emitUndefined for float bitsize: {d}", .{bits}), |
| 2012 | }, | 2085 | }, |
| 2013 | // As arrays point to linear memory, we cannot use 0xaaaaaaaa as the wasm | 2086 | .Array, .Struct => { |
| 2014 | // validator will not accept it due to out-of-bounds memory access); | | |
| 2015 | .Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))), | | |
| 2016 | .Struct => { | | |
| 2017 | // TODO: Write 0xaa struct's memory | | |
| 2018 | const result = try self.allocStack(ty); | 2087 | const result = try self.allocStack(ty); |
| | 2088 | const abi_size = ty.abiSize(self.target); |
| | 2089 | var offset: u32 = 0; |
| | 2090 | while (offset < abi_size) : (offset += 1) { |
| | 2091 | try self.emitWValue(result); |
| | 2092 | try self.addImm32(0xaa); |
| | 2093 | switch (self.ptrSize()) { |
| | 2094 | 4 => try self.addMemArg(.i32_store8, .{ .offset = offset, .alignment = 1 }), |
| | 2095 | 8 => try self.addMemArg(.i64_store8, .{ .offset = offset, .alignment = 1 }), |
| | 2096 | else => unreachable, |
| | 2097 | } |
| | 2098 | } |
| 2019 | try self.addLabel(.local_get, result.local); | 2099 | try self.addLabel(.local_get, result.local); |
| 2020 | }, | 2100 | }, |
| 2021 | .Pointer => switch (self.ptrSize()) { | 2101 | .Pointer => switch (self.ptrSize()) { |
| ... | @@ -2293,7 +2373,7 @@ fn structFieldPtr(self: *Self, struct_ptr: WValue, offset: u32) InnerError!WValu | ... | @@ -2293,7 +2373,7 @@ fn structFieldPtr(self: *Self, struct_ptr: WValue, offset: u32) InnerError!WValu |
| 2293 | }, | 2373 | }, |
| 2294 | else => unreachable, | 2374 | else => unreachable, |
| 2295 | }; | 2375 | }; |
| 2296 | return self.buildPointerOffset(.{ .local = local }, final_offset); | 2376 | return self.buildPointerOffset(.{ .local = local }, final_offset, .new); |
| 2297 | } | 2377 | } |
| 2298 | | 2378 | |
| 2299 | fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 2379 | fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | @@ -2528,7 +2608,7 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -2528,7 +2608,7 @@ fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2528 | const offset = err_ty.errorUnionSet().abiSize(self.target); | 2608 | const offset = err_ty.errorUnionSet().abiSize(self.target); |
| 2529 | | 2609 | |
| 2530 | const err_union = try self.allocStack(err_ty); | 2610 | const err_union = try self.allocStack(err_ty); |
| 2531 | const payload_ptr = try self.buildPointerOffset(err_union, offset); | 2611 | const payload_ptr = try self.buildPointerOffset(err_union, offset, .new); |
| 2532 | try self.store(payload_ptr, operand, op_ty, 0); | 2612 | try self.store(payload_ptr, operand, op_ty, 0); |
| 2533 | | 2613 | |
| 2534 | // ensure we also write '0' to the error part, so any present stack value gets overwritten by it. | 2614 | // ensure we also write '0' to the error part, so any present stack value gets overwritten by it. |
| ... | @@ -2620,7 +2700,7 @@ fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -2620,7 +2700,7 @@ fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2620 | const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target); | 2700 | const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target); |
| 2621 | | 2701 | |
| 2622 | if (isByRef(payload_ty)) { | 2702 | if (isByRef(payload_ty)) { |
| 2623 | return self.buildPointerOffset(operand, offset); | 2703 | return self.buildPointerOffset(operand, offset, .new); |
| 2624 | } | 2704 | } |
| 2625 | | 2705 | |
| 2626 | return self.load(operand, payload_ty, @intCast(u32, offset)); | 2706 | return self.load(operand, payload_ty, @intCast(u32, offset)); |
| ... | @@ -2640,7 +2720,7 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -2640,7 +2720,7 @@ fn airOptionalPayloadPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2640 | } | 2720 | } |
| 2641 | | 2721 | |
| 2642 | const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target); | 2722 | const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target); |
| 2643 | return self.buildPointerOffset(operand, offset); | 2723 | return self.buildPointerOffset(operand, offset, .new); |
| 2644 | } | 2724 | } |
| 2645 | | 2725 | |
| 2646 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 2726 | fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | @@ -2665,7 +2745,7 @@ fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue | ... | @@ -2665,7 +2745,7 @@ fn airOptionalPayloadPtrSet(self: *Self, inst: Air.Inst.Index) InnerError!WValue |
| 2665 | try self.addImm32(1); | 2745 | try self.addImm32(1); |
| 2666 | try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 }); | 2746 | try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 }); |
| 2667 | | 2747 | |
| 2668 | return self.buildPointerOffset(operand, offset); | 2748 | return self.buildPointerOffset(operand, offset, .new); |
| 2669 | } | 2749 | } |
| 2670 | | 2750 | |
| 2671 | fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 2751 | fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | @@ -2696,7 +2776,7 @@ fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | ... | @@ -2696,7 +2776,7 @@ fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 2696 | try self.addImm32(1); | 2776 | try self.addImm32(1); |
| 2697 | try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 }); | 2777 | try self.addMemArg(.i32_store8, .{ .offset = 0, .alignment = 1 }); |
| 2698 | | 2778 | |
| 2699 | const payload_ptr = try self.buildPointerOffset(result, offset); | 2779 | const payload_ptr = try self.buildPointerOffset(result, offset, .new); |
| 2700 | try self.store(payload_ptr, operand, payload_ty, 0); | 2780 | try self.store(payload_ptr, operand, payload_ty, 0); |
| 2701 | | 2781 | |
| 2702 | return result; | 2782 | return result; |
| ... | @@ -2952,7 +3032,11 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { | ... | @@ -2952,7 +3032,11 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 2952 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 3032 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 2953 | const ptr = self.resolveInst(bin_op.lhs); | 3033 | const ptr = self.resolveInst(bin_op.lhs); |
| 2954 | const offset = self.resolveInst(bin_op.rhs); | 3034 | const offset = self.resolveInst(bin_op.rhs); |
| 2955 | const pointee_ty = self.air.typeOf(bin_op.lhs).childType(); | 3035 | const ptr_ty = self.air.typeOf(bin_op.lhs); |
| | 3036 | const pointee_ty = switch (ptr_ty.ptrSize()) { |
| | 3037 | .One => ptr_ty.childType().childType(), // ptr to array, so get array element type |
| | 3038 | else => ptr_ty.childType(), |
| | 3039 | }; |
| 2956 | | 3040 | |
| 2957 | const valtype = try self.typeToValtype(Type.usize); | 3041 | const valtype = try self.typeToValtype(Type.usize); |
| 2958 | const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul }); | 3042 | const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul }); |
| ... | @@ -3036,3 +3120,29 @@ fn memSet(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void | ... | @@ -3036,3 +3120,29 @@ fn memSet(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void |
| 3036 | try self.endBlock(); | 3120 | try self.endBlock(); |
| 3037 | try self.endBlock(); | 3121 | try self.endBlock(); |
| 3038 | } | 3122 | } |
| | 3123 | |
| | 3124 | fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| | 3125 | if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; |
| | 3126 | |
| | 3127 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| | 3128 | const array_ty = self.air.typeOf(bin_op.lhs); |
| | 3129 | const array = self.resolveInst(bin_op.lhs); |
| | 3130 | const index = self.resolveInst(bin_op.rhs); |
| | 3131 | const elem_ty = array_ty.childType(); |
| | 3132 | const elem_size = elem_ty.abiSize(self.target); |
| | 3133 | |
| | 3134 | // calculate index into slice |
| | 3135 | try self.emitWValue(array); |
| | 3136 | try self.emitWValue(index); |
| | 3137 | try self.addImm32(@bitCast(i32, @intCast(u32, elem_size))); |
| | 3138 | try self.addTag(.i32_mul); |
| | 3139 | try self.addTag(.i32_add); |
| | 3140 | |
| | 3141 | const result = try self.allocLocal(elem_ty); |
| | 3142 | try self.addLabel(.local_set, result.local); |
| | 3143 | |
| | 3144 | if (isByRef(elem_ty)) { |
| | 3145 | return result; |
| | 3146 | } |
| | 3147 | return try self.load(result, elem_ty, 0); |
| | 3148 | } |