Skip to content

Commit

Permalink
Bug 1774830 - Add call_ref validation and baseline parsing. r=rhunt
Browse files Browse the repository at this point in the history
- Add simple tests
- Add validation support for call_ref
- Use typeIndex when function references are enabled
- Add a BaseCompiler::callRef

Differential Revision: https://phabricator.services.mozilla.com/D151727
  • Loading branch information
yurydelendik committed Jul 22, 2022
1 parent adac600 commit 0b5a7f0
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions js/src/jit-test/lib/wasm-binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const definedOpcodes =
...(wasmExceptionsEnabled() ? [0x06, 0x07, 0x08, 0x09] : []),
0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11,
...(wasmFunctionReferencesEnabled() ? [0x14] : []),
...(wasmExceptionsEnabled() ? [0x18, 0x19] : []),
0x1a, 0x1b, 0x1c,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
Expand Down
89 changes: 89 additions & 0 deletions js/src/jit-test/tests/wasm/function-references/call_ref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// |jit-test| skip-if: !wasmFunctionReferencesEnabled()

let { plusOne } = wasmEvalText(`(module
(; forward declaration so that ref.func works ;)
(elem declare $plusOneRef)
(func $plusOneRef (param i32) (result i32)
(i32.add
local.get 0
i32.const 1)
)
(func (export "plusOne") (param i32) (result i32)
local.get 0
ref.func $plusOneRef
call_ref
)
)`).exports;

assertEq(plusOne(3), 4);

// pass non-funcref type
wasmFailValidateText(`(module
(func (param $a i32)
local.get $a
call_ref
)
)`, /type mismatch: expression has type i32 but expected a reference type/);

wasmFailValidateText(`(module
(func (param $a (ref extern))
local.get $a
call_ref
)
)`, /type mismatch: reference expected to be a subtype of funcref/);

// pass (non-subtype of) funcref
wasmFailValidateText(`(module
(func (param funcref)
local.get 0
call_ref
)
)`, /type mismatch: reference expected to be a subtype of funcref/);

// signature mismatch
wasmFailValidateText(`(module
(elem declare $plusOneRef)
(func $plusOneRef (param f32) (result f32)
(f32.add
local.get 0
f32.const 1.0)
)
(func (export "plusOne") (param i32) (result i32)
local.get 0
ref.func $plusOneRef
call_ref
)
)`, /type mismatch/);

// TODO fix call_ref is not allowed in dead code
wasmFailValidateText(`(module
(func (param $a i32)
unreachable
call_ref
)
)`, /gc instruction temporarily not allowed in dead code/);

// Cross-instance calls
let { loadInt } = wasmEvalText(`(module
(memory 1 1)
(data (i32.const 0) "\\04\\00\\00\\00")
(func (export "loadInt") (result i32)
i32.const 0
i32.load offset=0
)
)`).exports;

let { callLoadInt } = wasmEvalText(`(module
(elem declare 0)
(import "" "loadInt" (func (result i32)))
(func (export "callLoadInt") (result i32)
ref.func 0
call_ref
)
)`, {"": { loadInt, }}).exports;

assertEq(loadInt(), 4);
assertEq(callLoadInt(), 4);
4 changes: 4 additions & 0 deletions js/src/wasm/WasmBCClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,9 @@ struct BaseCompiler final {
const Stk& indexVal, const FunctionCall& call,
CodeOffset* fastCallOffset, CodeOffset* slowCallOffset);
CodeOffset callImport(unsigned globalDataOffset, const FunctionCall& call);
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
CodeOffset callRef(const Stk& calleeRef, const FunctionCall& call);
#endif
CodeOffset builtinCall(SymbolicAddress builtin, const FunctionCall& call);
CodeOffset builtinInstanceMethodCall(const SymbolicAddressSignature& builtin,
const ABIArg& instanceArg,
Expand Down Expand Up @@ -1553,6 +1556,7 @@ struct BaseCompiler final {
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
[[nodiscard]] bool emitRefAsNonNull();
[[nodiscard]] bool emitBrOnNull();
[[nodiscard]] bool emitCallRef();
#endif

[[nodiscard]] bool emitAtomicCmpXchg(ValType type, Scalar::Type viewType);
Expand Down
73 changes: 73 additions & 0 deletions js/src/wasm/WasmBaselineCompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,17 @@ bool BaseCompiler::callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex,
return true;
}

#ifdef ENABLE_WASM_FUNCTION_REFERENCES
CodeOffset BaseCompiler::callRef(const Stk& calleeRef,
const FunctionCall& call) {
CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::FuncRef);
CalleeDesc callee = CalleeDesc::wasmFuncRef();

loadRef(calleeRef, RegRef(WasmCallRefReg));
return masm.wasmCallRef(desc, callee);
}
#endif

// Precondition: sync()

CodeOffset BaseCompiler::callImport(unsigned globalDataOffset,
Expand Down Expand Up @@ -4740,6 +4751,61 @@ bool BaseCompiler::emitCallIndirect() {
return pushCallResults(baselineCall, resultType, results);
}

#ifdef ENABLE_WASM_FUNCTION_REFERENCES
bool BaseCompiler::emitCallRef() {
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();

const FuncType* funcType;
Nothing unused_callee;
BaseNothingVector unused_args{};
if (!iter_.readCallRef(&funcType, &unused_callee, &unused_args)) {
return false;
}

if (deadCode_) {
return true;
}

sync();

// Stack: ... arg1 .. argn callee

uint32_t numArgs = funcType->args().length() + 1;
size_t stackArgBytes = stackConsumed(numArgs);

ResultType resultType(ResultType::Vector(funcType->results()));
StackResultsLoc results;
if (!pushStackResultsForCall(resultType, RegPtr(ABINonArgReg0), &results)) {
return false;
}

FunctionCall baselineCall(lineOrBytecode);
// State and realm are restored as needed by by callRef (really by
// MacroAssembler::wasmCallRef).
beginCall(baselineCall, UseABI::Wasm, RestoreRegisterStateAndRealm::False);

if (!emitCallArgs(funcType->args(), results, &baselineCall,
CalleeOnStack::True)) {
return false;
}

const Stk& callee = peek(results.count());
CodeOffset raOffset = callRef(callee, baselineCall);
if (!createStackMap("emitCallRef", raOffset)) {
return false;
}

popStackResultsAfterCall(results, stackArgBytes);

endCall(baselineCall, stackArgBytes);

popValueStackBy(numArgs);

captureCallResultRegisters(resultType);
return pushCallResults(baselineCall, resultType, results);
}
#endif

void BaseCompiler::emitRound(RoundingMode roundingMode, ValType operandType) {
if (operandType == ValType::F32) {
RegF32 f0 = popF32();
Expand Down Expand Up @@ -8578,6 +8644,13 @@ bool BaseCompiler::emitBody() {
CHECK_NEXT(emitCall());
case uint16_t(Op::CallIndirect):
CHECK_NEXT(emitCallIndirect());
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
case uint16_t(Op::CallRef):
if (!moduleEnv_.functionReferencesEnabled()) {
return iter_.unrecognizedOpcode(&op);
}
CHECK_NEXT(emitCallRef());
#endif

// Locals and globals
case uint16_t(Op::LocalGet):
Expand Down
1 change: 1 addition & 0 deletions js/src/wasm/WasmConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ enum class Op {
// Call operators
Call = 0x10,
CallIndirect = 0x11,
CallRef = 0x14,

// Additional exception operators
Delegate = 0x18,
Expand Down
4 changes: 4 additions & 0 deletions js/src/wasm/WasmOpIter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ OpKind wasm::Classify(OpBytes op) {
return OpKind::Call;
case Op::CallIndirect:
return OpKind::CallIndirect;
# ifdef ENABLE_WASM_FUNCTION_REFERENCES
case Op::CallRef:
return OpKind::CallRef;
# endif
case Op::Return:
case Op::Limit:
// Accept Limit, for use in decoding the end of a function after the body.
Expand Down
67 changes: 67 additions & 0 deletions js/src/wasm/WasmOpIter.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ enum class OpKind {
TeeGlobal,
Call,
CallIndirect,
# ifdef ENABLE_WASM_FUNCTION_REFERENCES
CallRef,
# endif
OldCallDirect,
OldCallIndirect,
Return,
Expand Down Expand Up @@ -364,6 +367,7 @@ class MOZ_STACK_CLASS OpIter : private Policy {
template <typename ValTypeSpanT>
[[nodiscard]] bool popWithTypes(ValTypeSpanT expected, ValueVector* values);
[[nodiscard]] bool popWithRefType(Value* value, StackType* type);
[[nodiscard]] bool popWithFuncType(Value* value, FuncType** funcType);
[[nodiscard]] bool popWithRttType(Value* rtt, uint32_t* rttTypeIndex,
uint32_t* rttDepth);
[[nodiscard]] bool popWithRttType(Value* rtt, uint32_t rttTypeIndex,
Expand Down Expand Up @@ -562,6 +566,10 @@ class MOZ_STACK_CLASS OpIter : private Policy {
[[nodiscard]] bool readCallIndirect(uint32_t* funcTypeIndex,
uint32_t* tableIndex, Value* callee,
ValueVector* argValues);
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
[[nodiscard]] bool readCallRef(const FuncType** funcType, Value* callee,
ValueVector* argValues);
#endif
[[nodiscard]] bool readOldCallDirect(uint32_t numFuncImports,
uint32_t* funcTypeIndex,
ValueVector* argValues);
Expand Down Expand Up @@ -905,6 +913,35 @@ inline bool OpIter<Policy>::popWithRefType(Value* value, StackType* type) {
return fail(error.get());
}

// This function pops exactly one value from the stack, checking that it is a
// subtype of the function type.
template <typename Policy>
inline bool OpIter<Policy>::popWithFuncType(Value* value, FuncType** funcType) {
StackType ty;
if (!popWithRefType(value, &ty)) {
return false;
}

if (ty.isBottom()) {
return fail("gc instruction temporarily not allowed in dead code");
}

if (!ty.valType().isTypeIndex() ||
!env_.types->isFuncType(ty.valType().typeIndex())) {
return fail("type mismatch: reference expected to be a subtype of funcref");
}

*funcType = &env_.types->funcType(ty.valType().typeIndex());

#ifdef WASM_PRIVATE_REFTYPES
if ((*funcType)->exposesTypeIndex()) {
return fail("cannot expose indexed reference type");
}
#endif

return true;
}

// This function pops exactly one value from the stack, checking that it is an
// rtt type with any type index or depth value.
template <typename Policy>
Expand Down Expand Up @@ -2213,6 +2250,15 @@ inline bool OpIter<Policy>::readRefFunc(uint32_t* funcIndex) {
return fail(
"function index is not declared in a section before the code section");
}

#ifdef ENABLE_WASM_FUNCTION_REFERENCES
// When function references enabled, push type index on the stack, e.g. for
// validation of the call_ref instruction.
if (env_.functionReferencesEnabled()) {
const uint32_t typeIndex = env_.funcs[*funcIndex].typeIndex;
return push(RefType::fromTypeIndex(typeIndex, false));
}
#endif
return push(RefType::func());
}

Expand Down Expand Up @@ -2374,6 +2420,27 @@ inline bool OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex,
return push(ResultType::Vector(funcType.results()));
}

#ifdef ENABLE_WASM_FUNCTION_REFERENCES
template <typename Policy>
inline bool OpIter<Policy>::readCallRef(const FuncType** funcType,
Value* callee, ValueVector* argValues) {
MOZ_ASSERT(Classify(op_) == OpKind::CallRef);

FuncType* funcType_;
if (!popWithFuncType(callee, &funcType_)) {
return false;
}

*funcType = funcType_;

if (!popCallArgs(funcType_->args(), argValues)) {
return false;
}

return push(ResultType::Vector(funcType_->results()));
}
#endif

template <typename Policy>
inline bool OpIter<Policy>::readOldCallDirect(uint32_t numFuncImports,
uint32_t* funcTypeIndex,
Expand Down
6 changes: 6 additions & 0 deletions js/src/wasm/WasmTypeDef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ TypeResult TypeContext::isRefSubtypeOf(RefType subType, RefType superType,
return TypeResult::True;
}

// The ref T <: funcref when T = func-type rule
if (subType.isTypeIndex() && types_[subType.typeIndex()].isFuncType() &&
superType.isFunc()) {
return TypeResult::True;
}

// Type-index references can be subtypes
if (subType.isTypeIndex() && superType.isTypeIndex()) {
return isTypeIndexSubtypeOf(subType.typeIndex(), superType.typeIndex(),
Expand Down
10 changes: 10 additions & 0 deletions js/src/wasm/WasmValidate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,16 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env,
CHECK(iter.readCallIndirect(&unusedIndex, &unusedIndex2, &nothing,
&unusedArgs));
}
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
case uint16_t(Op::CallRef): {
if (!env.functionReferencesEnabled()) {
return iter.unrecognizedOpcode(&op);
}
const FuncType* unusedType;
NothingVector unusedArgs{};
CHECK(iter.readCallRef(&unusedType, &nothing, &unusedArgs));
}
#endif
case uint16_t(Op::I32Const): {
int32_t unused;
CHECK(iter.readI32Const(&unused));
Expand Down

0 comments on commit 0b5a7f0

Please sign in to comment.