[wasm-gc] Implement 'let' opcode.

Changes:
- Implement the 'let' opcode, as per
https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#local-bindings
- Use a WasmDecoder in place of a plain decoder in OpcodeLength and
AnalyzeLoopAssignment.
- Change ControlBase to accept an additional 'locals_count' parameter.
- Implement required test infrastructure and write some simple tests.

Bug: v8:7748
Change-Id: I39d60d1f0c26016c8f89c009dc5f4119b0c73c87
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2204107
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67937}
This commit is contained in:
Manos Koukoutos 2020-05-20 22:29:59 +00:00 committed by Commit Bot
parent cf7731e261
commit 491a94b0ff
8 changed files with 227 additions and 14 deletions

View File

@ -1617,6 +1617,16 @@ class LiftoffCompiler {
LocalSet(imm.index, true);
}
void AllocateLocals(FullDecoder* decoder, Vector<Value> local_values) {
// TODO(7748): Introduce typed functions bailout reason
unsupported(decoder, kGC, "let");
}
void DeallocateLocals(FullDecoder* decoder, uint32_t count) {
// TODO(7748): Introduce typed functions bailout reason
unsupported(decoder, kGC, "let");
}
Register GetGlobalBaseAndOffset(const WasmGlobal* global,
LiftoffRegList* pinned, uint32_t* offset) {
Register addr = pinned->set(__ GetUnusedRegister(kGpReg, {})).gp();

View File

@ -729,7 +729,7 @@ struct Merge {
// Reachability::kReachable.
bool reached;
Merge(bool reached = false) : reached(reached) {}
explicit Merge(bool reached = false) : reached(reached) {}
Value& operator[](uint32_t i) {
DCHECK_GT(arity, i);
@ -742,6 +742,7 @@ enum ControlKind : uint8_t {
kControlIfElse,
kControlBlock,
kControlLoop,
kControlLet,
kControlTry,
kControlTryCatch
};
@ -759,6 +760,7 @@ enum Reachability : uint8_t {
template <typename Value>
struct ControlBase {
ControlKind kind = kControlBlock;
uint32_t locals_count = 0;
uint32_t stack_depth = 0; // stack height at the beginning of the construct.
const uint8_t* pc = nullptr;
Reachability reachability = kReachable;
@ -769,13 +771,16 @@ struct ControlBase {
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ControlBase);
ControlBase(ControlKind kind, uint32_t stack_depth, const uint8_t* pc,
Reachability reachability)
ControlBase(ControlKind kind, uint32_t locals_count, uint32_t stack_depth,
const uint8_t* pc, Reachability reachability)
: kind(kind),
locals_count(locals_count),
stack_depth(stack_depth),
pc(pc),
reachability(reachability),
start_merge(reachability == kReachable) {}
start_merge(reachability == kReachable) {
DCHECK(kind == kControlLet || locals_count == 0);
}
// Check whether the current block is reachable.
bool reachable() const { return reachability == kReachable; }
@ -795,6 +800,7 @@ struct ControlBase {
bool is_onearmed_if() const { return kind == kControlIf; }
bool is_if_else() const { return kind == kControlIfElse; }
bool is_block() const { return kind == kControlBlock; }
bool is_let() const { return kind == kControlLet; }
bool is_loop() const { return kind == kControlLoop; }
bool is_incomplete_try() const { return kind == kControlTry; }
bool is_try_catch() const { return kind == kControlTryCatch; }
@ -841,6 +847,8 @@ struct ControlBase {
F(LocalSet, const Value& value, const LocalIndexImmediate<validate>& imm) \
F(LocalTee, const Value& value, Value* result, \
const LocalIndexImmediate<validate>& imm) \
F(AllocateLocals, Vector<Value> local_values) \
F(DeallocateLocals, uint32_t count) \
F(GlobalGet, Value* result, const GlobalIndexImmediate<validate>& imm) \
F(GlobalSet, const Value& value, const GlobalIndexImmediate<validate>& imm) \
F(TableGet, const Value& index, Value* result, \
@ -1010,6 +1018,7 @@ class WasmDecoder : public Decoder {
}
*total_length += length;
if (insert_position.has_value()) {
// Move the insertion iterator to the end of the newly inserted locals.
insert_iterator =
local_types_->insert(insert_iterator, count, type) + count;
}
@ -1018,7 +1027,7 @@ class WasmDecoder : public Decoder {
return true;
}
static BitVector* AnalyzeLoopAssignment(Decoder* decoder, const byte* pc,
static BitVector* AnalyzeLoopAssignment(WasmDecoder* decoder, const byte* pc,
uint32_t locals_count, Zone* zone) {
if (pc >= decoder->end()) return nullptr;
if (*pc != kExprLoop) return nullptr;
@ -1385,7 +1394,7 @@ class WasmDecoder : public Decoder {
return true;
}
static uint32_t OpcodeLength(Decoder* decoder, const byte* pc) {
static uint32_t OpcodeLength(WasmDecoder* decoder, const byte* pc) {
WasmOpcode opcode = static_cast<WasmOpcode>(*pc);
switch (opcode) {
#define DECLARE_OPCODE_CASE(name, opcode, sig) case kExpr##name:
@ -1430,6 +1439,15 @@ class WasmDecoder : public Decoder {
return 1 + imm.length;
}
case kExprLet: {
BlockTypeImmediate<validate> imm(WasmFeatures::All(), decoder, pc);
uint32_t locals_length;
bool locals_result =
decoder->DecodeLocals(decoder->pc() + 1 + imm.length,
&locals_length, base::Optional<uint32_t>());
return 1 + imm.length + (locals_result ? locals_length : 0);
}
case kExprThrow: {
ExceptionIndexImmediate<validate> imm(decoder, pc);
return 1 + imm.length;
@ -1719,6 +1737,9 @@ class WasmDecoder : public Decoder {
case kExprReturnCallIndirect:
case kExprUnreachable:
return {0, 0};
case kExprLet:
// TODO(7748): Implement
return {0, 0};
case kNumericPrefix:
case kAtomicPrefix:
case kSimdPrefix: {
@ -2117,6 +2138,33 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
break;
}
case kExprLet: {
CHECK_PROTOTYPE_OPCODE(typed_funcref);
BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_);
if (!this->Validate(imm)) break;
uint32_t current_local_count =
static_cast<uint32_t>(local_type_vec_.size());
// Temporarily add the let-defined values
// to the beginning of the function locals.
uint32_t locals_length;
if (!this->DecodeLocals(this->pc() + 1 + imm.length, &locals_length,
0)) {
break;
}
len = 1 + imm.length + locals_length;
uint32_t locals_count = static_cast<uint32_t>(local_type_vec_.size() -
current_local_count);
ArgVector let_local_values =
PopArgs(VectorOf(local_type_vec_.data(), locals_count));
ArgVector args = PopArgs(imm.sig);
Control* let_block = PushControl(kControlLet, locals_count);
SetBlockType(let_block, imm, args.begin());
CALL_INTERFACE_IF_REACHABLE(Block, let_block);
PushMergeValues(let_block, &let_block->start_merge);
CALL_INTERFACE_IF_REACHABLE(AllocateLocals,
VectorOf(let_local_values));
break;
}
case kExprLoop: {
BlockTypeImmediate<validate> imm(this->enabled_, this, this->pc_);
if (!this->Validate(imm)) break;
@ -2182,6 +2230,12 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
if (!TypeCheckOneArmedIf(c)) break;
}
if (c->is_let()) {
this->local_types_->erase(
this->local_types_->begin(),
this->local_types_->begin() + c->locals_count);
CALL_INTERFACE_IF_REACHABLE(DeallocateLocals, c->locals_count);
}
if (!TypeCheckFallThru()) break;
if (control_.size() == 1) {
@ -2716,6 +2770,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
break;
case kControlIfElse:
case kControlTryCatch:
case kControlLet: // TODO(7748): Implement
break;
}
if (c.start_merge.arity) TRACE_PART("%u-", c.start_merge.arity);
@ -2818,15 +2873,24 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return args;
}
V8_INLINE ArgVector PopArgs(Vector<ValueType> arg_types) {
ArgVector args(arg_types.size());
for (int i = static_cast<int>(arg_types.size()) - 1; i >= 0; i--) {
args[i] = Pop(i, arg_types[i]);
}
return args;
}
ValueType GetReturnType(const FunctionSig* sig) {
DCHECK_GE(1, sig->return_count());
return sig->return_count() == 0 ? kWasmStmt : sig->GetReturn();
}
Control* PushControl(ControlKind kind) {
Control* PushControl(ControlKind kind, uint32_t locals_count = 0) {
Reachability reachability =
control_.empty() ? kReachable : control_.back().innerReachability();
control_.emplace_back(kind, stack_size(), this->pc_, reachability);
control_.emplace_back(kind, locals_count, stack_size(), this->pc_,
reachability);
return &control_.back();
}

View File

@ -64,7 +64,9 @@ DecodeResult VerifyWasmCode(AccountingAllocator* allocator,
}
unsigned OpcodeLength(const byte* pc, const byte* end) {
Decoder decoder(pc, end);
WasmFeatures no_features = WasmFeatures::None();
WasmDecoder<Decoder::kNoValidate> decoder(nullptr, no_features, &no_features,
nullptr, pc, end, 0);
return WasmDecoder<Decoder::kNoValidate>::OpcodeLength(&decoder, pc);
}
@ -293,7 +295,9 @@ bool PrintRawWasmCode(AccountingAllocator* allocator, const FunctionBody& body,
BitVector* AnalyzeLoopAssignmentForTesting(Zone* zone, size_t num_locals,
const byte* start, const byte* end) {
Decoder decoder(start, end);
WasmFeatures no_features = WasmFeatures::None();
WasmDecoder<Decoder::kValidate> decoder(nullptr, no_features, &no_features,
nullptr, start, end, 0);
return WasmDecoder<Decoder::kValidate>::AnalyzeLoopAssignment(
&decoder, start, static_cast<uint32_t>(num_locals), zone);
}

View File

@ -296,6 +296,19 @@ class WasmGraphBuildingInterface {
ssa_env_->locals[imm.index] = value.node;
}
void AllocateLocals(FullDecoder* decoder, Vector<Value> local_values) {
ZoneVector<TFNode*>* locals = &ssa_env_->locals;
locals->insert(locals->begin(), local_values.size(), nullptr);
for (uint32_t i = 0; i < local_values.size(); i++) {
(*locals)[i] = local_values[i].node;
}
}
void DeallocateLocals(FullDecoder* decoder, uint32_t count) {
ZoneVector<TFNode*>* locals = &ssa_env_->locals;
locals->erase(locals->begin(), locals->begin() + count);
}
void GlobalGet(FullDecoder* decoder, Value* result,
const GlobalIndexImmediate<validate>& imm) {
result->node = BUILD(GlobalGet, imm.index);

View File

@ -367,6 +367,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(RefCast, "ref.cast")
CASE_OP(BrOnCast, "br_on_cast")
CASE_OP(RefEq, "ref.eq")
CASE_OP(Let, "let")
case kNumericPrefix:

View File

@ -38,7 +38,8 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, const WasmFeatures&);
V(BrIf, 0x0d, _) \
V(BrTable, 0x0e, _) \
V(Return, 0x0f, _) \
V(BrOnNull, 0xd4, _) /* gc prototype */
V(Let, 0x17, _ /* gc prototype */) \
V(BrOnNull, 0xd4, _ /* gc prototype */)
// Constants, locals, globals, and calls.
#define FOREACH_MISC_OPCODE(V) \

View File

@ -158,6 +158,8 @@ WASM_EXEC_TEST(BasicStruct) {
n->EmitCode(n_code, sizeof(n_code));
// Result: 0b1001
/************************* End of test definitions *************************/
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
@ -165,10 +167,11 @@ WASM_EXEC_TEST(BasicStruct) {
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
Handle<WasmInstanceObject> instance =
MaybeHandle<WasmInstanceObject> maybe_instance =
testing::CompileAndInstantiateForTesting(
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()))
.ToHandleChecked();
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
if (thrower.error()) FATAL("%s", thrower.error_msg());
Handle<WasmInstanceObject> instance = maybe_instance.ToHandleChecked();
CHECK_EQ(42, testing::CallWasmFunctionForTesting(isolate, instance, &thrower,
"f", 0, nullptr));
@ -202,6 +205,106 @@ WASM_EXEC_TEST(BasicStruct) {
isolate, instance, &thrower, "n", 0, nullptr));
}
WASM_EXEC_TEST(LetInstruction) {
// TODO(7748): Implement support in other tiers.
if (execution_tier == ExecutionTier::kLiftoff) return;
if (execution_tier == ExecutionTier::kInterpreter) return;
TestSignatures sigs;
EXPERIMENTAL_FLAG_SCOPE(gc);
EXPERIMENTAL_FLAG_SCOPE(typed_funcref);
EXPERIMENTAL_FLAG_SCOPE(anyref);
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
WasmModuleBuilder* builder = new (&zone) WasmModuleBuilder(&zone);
StructType::Builder type_builder(&zone, 2);
type_builder.AddField(kWasmI32);
type_builder.AddField(kWasmI32);
int32_t type_index = builder->AddStructType(type_builder.Build());
ValueType kRefTypes[] = {ValueType(ValueType::kRef, type_index)};
FunctionSig sig_q_v(1, 0, kRefTypes);
WasmFunctionBuilder* let_test_1 = builder->AddFunction(sigs.i_v());
let_test_1->builder()->AddExport(CStrVector("let_test_1"), let_test_1);
uint32_t let_local_index = 0;
uint32_t let_field_index = 0;
byte let_code[] = {
WASM_LET_1_I(WASM_REF_TYPE(type_index),
WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(52)),
WASM_STRUCT_GET(type_index, let_field_index,
WASM_GET_LOCAL(let_local_index))),
kExprEnd};
let_test_1->EmitCode(let_code, sizeof(let_code));
WasmFunctionBuilder* let_test_2 = builder->AddFunction(sigs.i_v());
let_test_2->builder()->AddExport(CStrVector("let_test_2"), let_test_2);
uint32_t let_2_field_index = 0;
byte let_code_2[] = {
WASM_LET_2_I(kLocalI32, WASM_I32_ADD(WASM_I32V(42), WASM_I32V(-32)),
WASM_REF_TYPE(type_index),
WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(52)),
WASM_I32_MUL(WASM_STRUCT_GET(type_index, let_2_field_index,
WASM_GET_LOCAL(1)),
WASM_GET_LOCAL(0))),
kExprEnd};
let_test_2->EmitCode(let_code_2, sizeof(let_code_2));
WasmFunctionBuilder* let_test_locals = builder->AddFunction(sigs.i_i());
let_test_locals->builder()->AddExport(CStrVector("let_test_locals"),
let_test_locals);
let_test_locals->AddLocal(kWasmI32);
byte let_code_locals[] = {
WASM_SET_LOCAL(1, WASM_I32V(100)),
WASM_LET_2_I(
kLocalI32, WASM_I32V(1), kLocalI32, WASM_I32V(10),
WASM_I32_SUB(WASM_I32_ADD(WASM_GET_LOCAL(0), // 1st let-local
WASM_GET_LOCAL(2)), // Parameter
WASM_I32_ADD(WASM_GET_LOCAL(1), // 2nd let-local
WASM_GET_LOCAL(3)))), // Function local
kExprEnd};
// Result: (1 + 1000) - (10 + 100) = 891
let_test_locals->EmitCode(let_code_locals, sizeof(let_code_locals));
WasmFunctionBuilder* let_test_erase = builder->AddFunction(sigs.i_v());
let_test_erase->builder()->AddExport(CStrVector("let_test_erase"),
let_test_erase);
uint32_t let_erase_local_index = let_test_erase->AddLocal(kWasmI32);
byte let_code_erase[] = {WASM_SET_LOCAL(let_erase_local_index, WASM_I32V(0)),
WASM_LET_1_V(kLocalI32, WASM_I32V(1), WASM_NOP),
WASM_GET_LOCAL(let_erase_local_index), kExprEnd};
// The result should be 0 and not 1, as local_get(0) refers to the original
// local.
let_test_erase->EmitCode(let_code_erase, sizeof(let_code_erase));
/************************* End of test definitions *************************/
ZoneBuffer buffer(&zone);
builder->WriteTo(&buffer);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
testing::SetupIsolateForWasmModule(isolate);
ErrorThrower thrower(isolate, "Test");
MaybeHandle<WasmInstanceObject> maybe_instance =
testing::CompileAndInstantiateForTesting(
isolate, &thrower, ModuleWireBytes(buffer.begin(), buffer.end()));
if (thrower.error()) FATAL("%s", thrower.error_msg());
Handle<WasmInstanceObject> instance = maybe_instance.ToHandleChecked();
CHECK_EQ(42, testing::CallWasmFunctionForTesting(isolate, instance, &thrower,
"let_test_1", 0, nullptr));
CHECK_EQ(420, testing::CallWasmFunctionForTesting(isolate, instance, &thrower,
"let_test_2", 0, nullptr));
Handle<Object> let_local_args[] = {handle(Smi::FromInt(1000), isolate)};
CHECK_EQ(891, testing::CallWasmFunctionForTesting(isolate, instance, &thrower,
"let_test_locals", 1,
let_local_args));
CHECK_EQ(0, testing::CallWasmFunctionForTesting(
isolate, instance, &thrower, "let_test_erase", 0, nullptr));
}
WASM_EXEC_TEST(BasicArray) {
// TODO(7748): Implement support in other tiers.
if (execution_tier == ExecutionTier::kLiftoff) return;

View File

@ -458,6 +458,23 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define WASM_RETURN_CALL_INDIRECT(sig_index, ...) \
__VA_ARGS__, kExprReturnCallIndirect, static_cast<byte>(sig_index), TABLE_ZERO
#define WASM_REF_TYPE(typeidx) kLocalRef, U32V_1(typeidx)
// shift locals by 1; let (locals[0]: local_type) = value in ...
#define WASM_LET_1_V(local_type, value, ...) \
value, kExprLet, kLocalVoid, U32V_1(1), U32V_1(1), local_type, __VA_ARGS__, \
kExprEnd
#define WASM_LET_1_I(local_type, value, ...) \
value, kExprLet, kLocalI32, U32V_1(1), U32V_1(1), local_type, __VA_ARGS__, \
kExprEnd
// shift locals by 2;
// let (locals[0]: local_type_1) = value_1,
// (locals[1]: local_type_2) = value_2
// in ...
#define WASM_LET_2_I(local_type_1, value_1, local_type_2, value_2, ...) \
value_1, value_2, kExprLet, kLocalI32, U32V_1(2), U32V_1(1), local_type_1, \
U32V_1(1), local_type_2, __VA_ARGS__, kExprEnd
#define WASM_NOT(x) x, kExprI32Eqz
#define WASM_SEQ(...) __VA_ARGS__