Commit 3f9f569c authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm-gc] Implement array.init_from_data

Bug: v8:7748
Change-Id: Iee5afc3ce21f3a09fdb810beb6a73123bf21afdf
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3401594Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78766}
parent 1a4239f5
......@@ -37,6 +37,8 @@ extern runtime WasmI64AtomicWait(
extern runtime WasmAllocateRtt(Context, Smi, Map, Smi): Map;
extern runtime WasmArrayCopy(
Context, WasmArray, Smi, WasmArray, Smi, Smi): JSAny;
extern runtime WasmArrayInitFromData(
Context, WasmInstanceObject, Smi, Smi, Smi, Map): Object;
}
namespace unsafe {
......@@ -364,6 +366,14 @@ builtin WasmAllocateArray_InitNull(
rtt, length, elementSize, InitializationMode::kInitializeToNull);
}
builtin WasmArrayInitFromData(
dataSegment: uint32, offset: uint32, length: uint32, rtt: Map): Object {
const instance = LoadInstanceFromFrame();
tail runtime::WasmArrayInitFromData(
LoadContextFromInstance(instance), instance, SmiFromUint32(dataSegment),
SmiFromUint32(offset), SmiFromUint32(length), rtt);
}
// We put all uint32 parameters at the beginning so that they are assigned to
// registers.
builtin WasmArrayCopyWithChecks(
......
......@@ -230,6 +230,13 @@ class WasmGraphAssembler : public GraphAssembler {
return graph()->NewNode(mcgraph()->common()->NumberConstant(value));
}
Node* SmiConstant(Tagged_t value) {
Address tagged_value = Internals::IntToSmi(static_cast<int>(value));
return kTaggedSize == kInt32Size
? Int32Constant(static_cast<int32_t>(tagged_value))
: Int64Constant(static_cast<int64_t>(tagged_value));
}
// Helper functions for dealing with HeapObjects.
// Rule of thumb: if access to a given field in an object is required in
// at least two places, put a helper function here.
......@@ -5621,6 +5628,27 @@ Node* WasmGraphBuilder::ArrayInit(const wasm::ArrayType* type, Node* rtt,
return array;
}
Node* WasmGraphBuilder::ArrayInitFromData(const wasm::ArrayType* type,
uint32_t data_segment, Node* offset,
Node* length, Node* rtt,
wasm::WasmCodePosition position) {
Node* array = gasm_->CallBuiltin(
Builtin::kWasmArrayInitFromData, Operator::kNoDeopt | Operator::kNoThrow,
gasm_->Uint32Constant(data_segment), offset, length, rtt);
TrapIfTrue(wasm::kTrapArrayTooLarge,
gasm_->TaggedEqual(
array, gasm_->SmiConstant(
wasm::kArrayInitFromDataArrayTooLargeErrorCode)),
position);
TrapIfTrue(
wasm::kTrapDataSegmentOutOfBounds,
gasm_->TaggedEqual(
array, gasm_->SmiConstant(
wasm::kArrayInitFromDataSegmentOutOfBoundsErrorCode)),
position);
return array;
}
Node* WasmGraphBuilder::RttCanon(uint32_t type_index) {
Node* maps_list =
LOAD_INSTANCE_FIELD(ManagedObjectMaps, MachineType::TaggedPointer());
......
......@@ -507,6 +507,9 @@ class WasmGraphBuilder {
Node* length, wasm::WasmCodePosition position);
Node* ArrayInit(const wasm::ArrayType* type, Node* rtt,
base::Vector<Node*> elements);
Node* ArrayInitFromData(const wasm::ArrayType* type, uint32_t data_segment,
Node* offset, Node* length, Node* rtt,
wasm::WasmCodePosition position);
Node* I31New(Node* input);
Node* I31GetS(Node* input);
Node* I31GetU(Node* input);
......
......@@ -1651,6 +1651,27 @@ Handle<WasmArray> Factory::NewWasmArrayFromElements(
return handle(result, isolate());
}
Handle<WasmArray> Factory::NewWasmArrayFromMemory(uint32_t length,
Handle<Map> map,
Address source) {
wasm::ValueType element_type = reinterpret_cast<wasm::ArrayType*>(
map->wasm_type_info().foreign_address())
->element_type();
DCHECK(element_type.is_numeric());
HeapObject raw =
AllocateRaw(WasmArray::SizeFor(*map, length), AllocationType::kYoung);
DisallowGarbageCollection no_gc;
raw.set_map_after_allocation(*map);
WasmArray result = WasmArray::cast(raw);
result.set_raw_properties_or_hash(*empty_fixed_array(), kRelaxedStore);
result.set_length(length);
MemCopy(reinterpret_cast<void*>(result.ElementAddress(0)),
reinterpret_cast<void*>(source),
length * element_type.element_size_bytes());
return handle(result, isolate());
}
Handle<WasmStruct> Factory::NewWasmStruct(const wasm::StructType* type,
wasm::WasmValue* args,
Handle<Map> map) {
......
......@@ -618,6 +618,8 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<WasmArray> NewWasmArrayFromElements(
const wasm::ArrayType* type, const std::vector<wasm::WasmValue>& elements,
Handle<Map> map);
Handle<WasmArray> NewWasmArrayFromMemory(uint32_t length, Handle<Map> map,
Address source);
Handle<SharedFunctionInfo> NewSharedFunctionInfoForWasmExportedFunction(
Handle<String> name, Handle<WasmExportedFunctionData> data);
......
......@@ -695,6 +695,37 @@ RUNTIME_FUNCTION(Runtime_WasmArrayCopy) {
return ReadOnlyRoots(isolate).undefined_value();
}
// Returns
// - the new array if the operation succeeds,
// - Smi(0) if the requested array length is too large,
// - Smi(1) if the data segment ran out-of-bounds.
RUNTIME_FUNCTION(Runtime_WasmArrayInitFromData) {
ClearThreadInWasmScope flag_scope(isolate);
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 0);
CONVERT_UINT32_ARG_CHECKED(data_segment, 1);
CONVERT_UINT32_ARG_CHECKED(offset, 2);
CONVERT_UINT32_ARG_CHECKED(length, 3);
CONVERT_ARG_HANDLE_CHECKED(Map, rtt, 4);
uint32_t element_size = WasmArray::DecodeElementSizeFromMap(*rtt);
uint32_t length_in_bytes = length * element_size;
if (length > static_cast<uint32_t>(WasmArray::MaxLength(element_size))) {
return Smi::FromInt(wasm::kArrayInitFromDataArrayTooLargeErrorCode);
}
// The check above implies no overflow.
DCHECK_EQ(length_in_bytes / element_size, length);
if (!base::IsInBounds<uint32_t>(
offset, length_in_bytes,
instance->data_segment_sizes()[data_segment])) {
return Smi::FromInt(wasm::kArrayInitFromDataSegmentOutOfBoundsErrorCode);
}
Address source = instance->data_segment_starts()[data_segment] + offset;
return *isolate->factory()->NewWasmArrayFromMemory(length, rtt, source);
}
namespace {
// Synchronize the stack limit with the active continuation for stack-switching.
// This can be done before or after changing the stack pointer itself, as long
......
......@@ -588,6 +588,7 @@ namespace internal {
F(WasmDebugBreak, 0, 1) \
F(WasmAllocateRtt, 3, 1) \
F(WasmArrayCopy, 5, 1) \
F(WasmArrayInitFromData, 5, 1) \
F(WasmAllocateContinuation, 1, 1) \
F(WasmSyncStackLimit, 0, 1)
......
......@@ -121,7 +121,10 @@ inline void Store(LiftoffAssembler* assm, Register base, int32_t offset,
case kS128:
assm->movdqu(dst, src.fp());
break;
default:
case kVoid:
case kBottom:
case kI8:
case kI16:
UNREACHABLE();
}
}
......@@ -132,6 +135,8 @@ inline void push(LiftoffAssembler* assm, LiftoffRegister reg, ValueKind kind,
case kI32:
case kRef:
case kOptRef:
case kRtt:
case kRttWithDepth:
assm->AllocateStackSpace(padding);
assm->push(reg.gp());
break;
......@@ -152,7 +157,10 @@ inline void push(LiftoffAssembler* assm, LiftoffRegister reg, ValueKind kind,
assm->AllocateStackSpace(sizeof(double) * 2 + padding);
assm->movdqu(Operand(esp, 0), reg.fp());
break;
default:
case kVoid:
case kBottom:
case kI8:
case kI16:
UNREACHABLE();
}
}
......
......@@ -5355,6 +5355,45 @@ class LiftoffCompiler {
__ PushRegister(kRef, array);
}
void ArrayInitFromData(FullDecoder* decoder,
const ArrayIndexImmediate<validate>& array_imm,
const IndexImmediate<validate>& data_segment,
const Value& /* offset */, const Value& /* length */,
const Value& /* rtt */, Value* /* result */) {
LiftoffRegList pinned;
LiftoffRegister data_segment_reg =
pinned.set(__ GetUnusedRegister(kGpReg, pinned));
__ LoadConstant(data_segment_reg,
WasmValue(static_cast<int32_t>(data_segment.index)));
LiftoffAssembler::VarState data_segment_var(kI32, data_segment_reg, 0);
CallRuntimeStub(WasmCode::kWasmArrayInitFromData,
MakeSig::Returns(kRef).Params(kI32, kI32, kI32, kRtt),
{
data_segment_var,
__ cache_state()->stack_state.end()[-3], // offset
__ cache_state()->stack_state.end()[-2], // length
__ cache_state()->stack_state.end()[-1] // rtt
},
decoder->position());
LiftoffRegister result(kReturnRegister0);
// Reuse the data segment register for error handling.
LiftoffRegister error_smi = data_segment_reg;
LoadSmi(error_smi, kArrayInitFromDataArrayTooLargeErrorCode);
Label* trap_label_array_too_large =
AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapArrayTooLarge);
__ emit_cond_jump(kEqual, trap_label_array_too_large, kRef, result.gp(),
error_smi.gp());
LoadSmi(error_smi, kArrayInitFromDataSegmentOutOfBoundsErrorCode);
Label* trap_label_segment_out_of_bounds = AddOutOfLineTrap(
decoder, WasmCode::kThrowWasmTrapDataSegmentOutOfBounds);
__ emit_cond_jump(kEqual, trap_label_segment_out_of_bounds, kRef,
result.gp(), error_smi.gp());
__ PushRegister(kRef, result);
}
// 1 bit Smi tag, 31 bits Smi shift, 1 bit i31ref high-bit truncation.
constexpr static int kI31To32BitSmiShift = 33;
......
......@@ -940,6 +940,9 @@ struct ControlBase : public PcForErrors<validate> {
const Value& rtt, Value* result) \
F(ArrayInit, const ArrayIndexImmediate<validate>& imm, \
const base::Vector<Value>& elements, const Value& rtt, Value* result) \
F(ArrayInitFromData, const ArrayIndexImmediate<validate>& array_imm, \
const IndexImmediate<validate>& data_segment, const Value& offset, \
const Value& length, const Value& rtt, Value* result) \
F(RttCanon, uint32_t type_index, Value* result) \
F(RttSub, uint32_t type_index, const Value& parent, Value* result, \
WasmRttSubMode mode) \
......@@ -1882,6 +1885,12 @@ class WasmDecoder : public Decoder {
pc + length + dst_imm.length);
return length + dst_imm.length + src_imm.length;
}
case kExprArrayInitFromDataStatic: {
ArrayIndexImmediate<validate> array_imm(decoder, pc + length);
IndexImmediate<validate> data_imm(
decoder, pc + length + array_imm.length, "data segment index");
return length + array_imm.length + data_imm.length;
}
case kExprBrOnCast:
case kExprBrOnCastFail:
case kExprBrOnData:
......@@ -2070,6 +2079,7 @@ class WasmDecoder : public Decoder {
return {2, 0};
case kExprArrayNew:
case kExprArrayNewDefaultWithRtt:
case kExprArrayInitFromDataStatic:
case kExprArrayGet:
case kExprArrayGetS:
case kExprArrayGetU:
......@@ -4268,6 +4278,48 @@ class WasmFullDecoder : public WasmDecoder<validate, decoding_mode> {
Push(value);
return opcode_length + imm.length;
}
case kExprArrayInitFromDataStatic: {
ArrayIndexImmediate<validate> array_imm(this,
this->pc_ + opcode_length);
if (!this->Validate(this->pc_ + opcode_length, array_imm)) return 0;
ValueType element_type = array_imm.array_type->element_type();
if (element_type.is_reference()) {
this->DecodeError(
"array.init_from_data can only be used with value-type arrays, "
"found array type #%d instead",
array_imm.index);
return 0;
}
#if V8_TARGET_BIG_ENDIAN
// Byte sequences in data segments are interpreted as little endian for
// the purposes of this instruction. This means that those will have to
// be transformed in big endian architectures. TODO(7748): Implement.
if (element_type.element_size_bytes() > 1) {
UNIMPLEMENTED();
}
#endif
const byte* data_index_pc =
this->pc_ + opcode_length + array_imm.length;
IndexImmediate<validate> data_segment(this, data_index_pc,
"data segment");
if (!this->ValidateDataSegment(data_index_pc, data_segment)) return 0;
Value length = Peek(0, 1, kWasmI32);
Value offset = Peek(1, 0, kWasmI32);
Value rtt = CreateValue(ValueType::Rtt(array_imm.index));
CALL_INTERFACE_IF_OK_AND_REACHABLE(RttCanon, array_imm.index, &rtt);
Push(rtt);
Value array =
CreateValue(ValueType::Ref(array_imm.index, kNonNullable));
CALL_INTERFACE_IF_OK_AND_REACHABLE(ArrayInitFromData, array_imm,
data_segment, offset, length, rtt,
&array);
Drop(3); // rtt, length, offset
Push(array);
return opcode_length + array_imm.length + data_segment.length;
}
case kExprArrayGetS:
case kExprArrayGetU: {
NON_CONST_ONLY
......
......@@ -1112,6 +1112,16 @@ class WasmGraphBuildingInterface {
builder_->ArrayInit(imm.array_type, rtt.node, VectorOf(element_nodes));
}
void ArrayInitFromData(FullDecoder* decoder,
const ArrayIndexImmediate<validate>& array_imm,
const IndexImmediate<validate>& data_segment,
const Value& offset, const Value& length,
const Value& rtt, Value* result) {
result->node = builder_->ArrayInitFromData(
array_imm.array_type, data_segment.index, offset.node, length.node,
rtt.node, decoder->position());
}
void I31New(FullDecoder* decoder, const Value& input, Value* result) {
result->node = builder_->I31New(input.node);
}
......
......@@ -154,6 +154,39 @@ void InitExprInterface::ArrayInit(FullDecoder* decoder,
ValueType::Ref(HeapType(imm.index), kNonNullable));
}
void InitExprInterface::ArrayInitFromData(
FullDecoder* decoder, const ArrayIndexImmediate<validate>& array_imm,
const IndexImmediate<validate>& data_segment_imm, const Value& offset_value,
const Value& length_value, const Value& rtt, Value* result) {
if (!generate_result()) return;
uint32_t length = length_value.runtime_value.to_u32();
uint32_t offset = offset_value.runtime_value.to_u32();
const WasmDataSegment& data_segment =
module_->data_segments[data_segment_imm.index];
uint32_t length_in_bytes =
length * array_imm.array_type->element_type().element_size_bytes();
// Error handling.
if (length >
static_cast<uint32_t>(WasmArray::MaxLength(array_imm.array_type))) {
error_ = "length for array.init_from_data too large";
return;
}
if (!base::IsInBounds<uint32_t>(offset, length_in_bytes,
data_segment.source.length())) {
error_ = "data segment is out of bounds";
return;
}
Address source =
instance_->data_segment_starts()[data_segment_imm.index] + offset;
Handle<WasmArray> array_value = isolate_->factory()->NewWasmArrayFromMemory(
length, Handle<Map>::cast(rtt.runtime_value.to_ref()), source);
result->runtime_value = WasmValue(
array_value, ValueType::Ref(HeapType(array_imm.index), kNonNullable));
}
void InitExprInterface::RttCanon(FullDecoder* decoder, uint32_t type_index,
Value* result) {
if (!generate_result()) return;
......
......@@ -120,6 +120,7 @@ struct WasmModule;
V(WasmAllocateArray_InitZero) \
V(WasmArrayCopy) \
V(WasmArrayCopyWithChecks) \
V(WasmArrayInitFromData) \
V(WasmAllocateRtt) \
V(WasmAllocateFreshRtt) \
V(WasmAllocateStructWithRtt) \
......
......@@ -171,6 +171,9 @@ constexpr uint32_t kMinimumSupertypeArraySize = 3;
constexpr int32_t kOSRTargetOffset = 5 * kSystemPointerSize;
#endif
constexpr Tagged_t kArrayInitFromDataArrayTooLargeErrorCode = 0;
constexpr Tagged_t kArrayInitFromDataSegmentOutOfBoundsErrorCode = 1;
} // namespace wasm
} // namespace internal
} // namespace v8
......
......@@ -967,15 +967,15 @@ class WasmArray : public TorqueGeneratedWasmArray<WasmArray, WasmObject> {
inline uint32_t element_offset(uint32_t index);
inline Address ElementAddress(uint32_t index);
static int MaxLength(uint32_t element_size_log2) {
static int MaxLength(uint32_t element_size_bytes) {
// The total object size must fit into a Smi, for filler objects. To make
// the behavior of Wasm programs independent from the Smi configuration,
// we hard-code the smaller of the two supported ranges.
return (SmiTagging<4>::kSmiMaxValue - kHeaderSize) >> element_size_log2;
return (SmiTagging<4>::kSmiMaxValue - kHeaderSize) / element_size_bytes;
}
static int MaxLength(const wasm::ArrayType* type) {
return MaxLength(type->element_type().element_size_log2());
return MaxLength(type->element_type().element_size_bytes());
}
static inline void EncodeElementSizeInMap(int element_size, Map map);
......
......@@ -414,6 +414,7 @@ constexpr const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(ArrayCopy, "array.copy")
CASE_OP(ArrayInit, "array.init")
CASE_OP(ArrayInitStatic, "array.init_static")
CASE_OP(ArrayInitFromDataStatic, "array.init_from_data")
CASE_OP(I31New, "i31.new")
CASE_OP(I31GetS, "i31.get_s")
CASE_OP(I31GetU, "i31.get_u")
......
......@@ -686,6 +686,7 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig,
V(ArrayInitStatic, 0xfb1a, _) \
V(ArrayNew, 0xfb1b, _) \
V(ArrayNewDefault, 0xfb1c, _) \
V(ArrayInitFromDataStatic, 0xfb1d, _) /* n/s - V8 experimental */ \
V(I31New, 0xfb20, _) \
V(I31GetS, 0xfb21, _) \
V(I31GetU, 0xfb22, _) \
......
......@@ -408,6 +408,15 @@ class InitExprInterface {
: WasmInitExpr::ArrayInit(imm.index, args);
}
void ArrayInitFromData(FullDecoder* decoder,
const ArrayIndexImmediate<validate>& array_imm,
const IndexImmediate<validate>& data_segment_imm,
const Value& offset_value, const Value& length_value,
const Value& rtt, Value* result) {
// TODO(7748): Implement.
UNIMPLEMENTED();
}
void RttCanon(FullDecoder* decoder, uint32_t type_index, Value* result) {
result->init_expr = WasmInitExpr::RttCanon(type_index);
}
......
......@@ -54,3 +54,61 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
() => builder.instantiate(), WebAssembly.CompileError,
/subtyping depth is greater than allowed/);
})();
(function TestArrayInitFromDataStatic() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let array_type_index = builder.addArraySubtype(kWasmI16, true);
let dummy_byte = 0xff;
let element_0 = 1000;
let element_1 = -2222;
let data_segment = builder.addPassiveDataSegment(
[dummy_byte, element_0 & 0xff, (element_0 >> 8) & 0xff,
element_1 & 0xff, (element_1 >> 8) & 0xff]);
let global = builder.addGlobal(
wasmRefType(array_type_index), true,
WasmInitExpr.ArrayInitStaticFromData(
array_type_index, data_segment,
[WasmInitExpr.I32Const(1), WasmInitExpr.I32Const(2)], builder));
builder.addFunction("global_get", kSig_i_i)
.addBody([
kExprGlobalGet, global.index,
kExprLocalGet, 0,
kGCPrefix, kExprArrayGetS, array_type_index])
.exportFunc();
// parameters: (segment offset, array length, array index)
builder.addFunction("init_from_data", kSig_i_iii)
.addBody([
kExprLocalGet, 0, kExprLocalGet, 1,
kGCPrefix, kExprArrayInitFromDataStatic,
array_type_index, data_segment,
kExprLocalGet, 2,
kGCPrefix, kExprArrayGetS, array_type_index])
.exportFunc();
builder.addFunction("drop_segment", kSig_v_v)
.addBody([kNumericPrefix, kExprDataDrop, data_segment])
.exportFunc();
let instance = builder.instantiate();
assertEquals(element_0, instance.exports.global_get(0));
assertEquals(element_1, instance.exports.global_get(1));
let init = instance.exports.init_from_data;
assertEquals(element_0, init(1, 2, 0));
assertEquals(element_1, init(1, 2, 1));
assertTraps(kTrapArrayTooLarge, () => init(1, 1000000000, 0));
assertTraps(kTrapDataSegmentOutOfBounds, () => init(2, 2, 0));
instance.exports.drop_segment();
assertTraps(kTrapDataSegmentOutOfBounds, () => init(1, 2, 0));
})();
......@@ -490,6 +490,7 @@ let kExprArrayInit = 0x19;
let kExprArrayInitStatic = 0x1a;
let kExprArrayNew = 0x1b;
let kExprArrayNewDefault = 0x1c;
let kExprArrayInitFromDataStatic = 0x1d;
let kExprI31New = 0x20;
let kExprI31GetS = 0x21;
let kExprI31GetU = 0x22;
......@@ -1044,6 +1045,15 @@ class Binary {
this.emit_u32v(expr.value);
this.emit_u32v(expr.operands.length - 1);
break;
case kExprArrayInitFromDataStatic:
for (let operand of expr.operands) {
this.emit_init_expr_recursive(operand);
}
this.emit_u8(kGCPrefix);
this.emit_u8(expr.kind);
this.emit_u32v(expr.array_index);
this.emit_u32v(expr.data_segment);
break;
case kExprRttCanon:
this.emit_u8(kGCPrefix);
this.emit_u8(kExprRttCanon);
......@@ -1208,6 +1218,13 @@ class WasmInitExpr {
static ArrayInitStatic(type, args) {
return {kind: kExprArrayInitStatic, value: type, operands: args};
}
static ArrayInitStaticFromData(array_index, data_segment, args, builder) {
// array.init_from_data means we need to pull the data count section before
// any section that may include init. expressions.
builder.early_data_count_section = true;
return {kind: kExprArrayInitFromDataStatic, array_index: array_index,
data_segment: data_segment, operands: args};
}
static RttCanon(type) {
return {kind: kExprRttCanon, value: type};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment