Skip to content

Commit 67ed68f

Browse files
committed
Bug 1546138 - Wasm: Implement 'ref.func'. r=bbouvier
This commit implements the 'ref.func' instruction by emitting an instance call to WasmInstanceObject::getExportedFunction. The referenced function must be used in an element segment to validate. See [1] for more details. [1] WebAssembly/reference-types#31 Differential Revision: https://phabricator.services.mozilla.com/D40586 --HG-- extra : moz-landing-system : lando
1 parent fefd742 commit 67ed68f

14 files changed

+262
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// |jit-test| skip-if: !wasmReftypesEnabled()
2+
3+
// 'ref.func' parses, validates and returns a non-null value
4+
wasmFullPass(`
5+
(module
6+
(elem declared $run)
7+
(func $run (result i32)
8+
ref.func $run
9+
ref.is_null
10+
)
11+
(export "run" $run)
12+
)
13+
`, 0);
14+
15+
// function returning reference to itself
16+
{
17+
let {f1} = wasmEvalText(`
18+
(module
19+
(elem declared $f1)
20+
(func $f1 (result funcref) ref.func $f1)
21+
(export "f1" $f1)
22+
)
23+
`).exports;
24+
assertEq(f1(), f1);
25+
}
26+
27+
// function returning reference to a different function
28+
{
29+
let {f1, f2} = wasmEvalText(`
30+
(module
31+
(elem declared $f1)
32+
(func $f1)
33+
(func $f2 (result funcref) ref.func $f1)
34+
(export "f1" $f1)
35+
(export "f2" $f2)
36+
)
37+
`).exports;
38+
assertEq(f2(), f1);
39+
}
40+
41+
// function returning reference to function in a different module
42+
{
43+
let i1 = wasmEvalText(`
44+
(module
45+
(elem declared $f1)
46+
(func $f1)
47+
(export "f1" $f1)
48+
)
49+
`);
50+
let i2 = wasmEvalText(`
51+
(module
52+
(import $f1 "" "f1" (func))
53+
(elem declared $f1)
54+
(func $f2 (result funcref) ref.func $f1)
55+
(export "f1" $f1)
56+
(export "f2" $f2)
57+
)
58+
`, {"": i1.exports});
59+
60+
let f1 = i1.exports.f1;
61+
let f2 = i2.exports.f2;
62+
assertEq(f2(), f1);
63+
}
64+
65+
// function index must be valid
66+
assertErrorMessage(() => {
67+
wasmEvalText(`
68+
(module
69+
(func (result funcref) ref.func 10)
70+
)
71+
`);
72+
}, WebAssembly.CompileError, /function index out of range/);
73+
74+
function validFuncRefText(forwardDeclare) {
75+
return wasmEvalText(`
76+
(module
77+
(table 1 funcref)
78+
(func $test (result funcref) ref.func $referenced)
79+
(func $referenced)
80+
${forwardDeclare}
81+
)
82+
`);
83+
}
84+
85+
// referenced function must be forward declared somehow
86+
assertErrorMessage(() => validFuncRefText(''), WebAssembly.CompileError, /function index is not in an element segment/);
87+
88+
// referenced function can be forward declared via segments
89+
assertEq(validFuncRefText('(elem 0 (i32.const 0) $referenced)') instanceof WebAssembly.Instance, true);
90+
assertEq(validFuncRefText('(elem passive $referenced)') instanceof WebAssembly.Instance, true);
91+
assertEq(validFuncRefText('(elem declared $referenced)') instanceof WebAssembly.Instance, true);
92+
93+
// referenced function cannot be forward declared via start section or export
94+
assertErrorMessage(() => validFuncRefText('(start $referenced)'), WebAssembly.CompileError, /function index is not in an element segment/);
95+
assertErrorMessage(() => validFuncRefText('(export "referenced" $referenced)'), WebAssembly.CompileError, /function index is not in an element segment/);

js/src/wasm/WasmAST.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ enum class AstExprKind {
417417
MemorySize,
418418
Nop,
419419
Pop,
420+
RefFunc,
420421
RefNull,
421422
Return,
422423
SetGlobal,
@@ -1576,6 +1577,17 @@ class AstExtraConversionOperator final : public AstExpr {
15761577
AstExpr* operand() const { return operand_; }
15771578
};
15781579

1580+
class AstRefFunc final : public AstExpr {
1581+
AstRef func_;
1582+
1583+
public:
1584+
static const AstExprKind Kind = AstExprKind::RefFunc;
1585+
explicit AstRefFunc(AstRef func)
1586+
: AstExpr(Kind, ExprType::FuncRef), func_(func) {}
1587+
1588+
AstRef& func() { return func_; }
1589+
};
1590+
15791591
class AstRefNull final : public AstExpr {
15801592
public:
15811593
static const AstExprKind Kind = AstExprKind::RefNull;

js/src/wasm/WasmBaselineCompile.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6842,6 +6842,7 @@ class BaseCompiler final : public BaseCompilerInterface {
68426842
MOZ_MUST_USE bool emitMemoryGrow();
68436843
MOZ_MUST_USE bool emitMemorySize();
68446844

6845+
MOZ_MUST_USE bool emitRefFunc();
68456846
MOZ_MUST_USE bool emitRefNull();
68466847
void emitRefIsNull();
68476848

@@ -9858,6 +9859,20 @@ bool BaseCompiler::emitMemorySize() {
98589859
return emitInstanceCall(lineOrBytecode, SASigMemorySize);
98599860
}
98609861

9862+
bool BaseCompiler::emitRefFunc() {
9863+
uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
9864+
uint32_t funcIndex;
9865+
if (!iter_.readRefFunc(&funcIndex)) {
9866+
return false;
9867+
}
9868+
if (deadCode_) {
9869+
return true;
9870+
}
9871+
9872+
pushI32(funcIndex);
9873+
return emitInstanceCall(lineOrBytecode, SASigFuncRef);
9874+
}
9875+
98619876
bool BaseCompiler::emitRefNull() {
98629877
if (!iter_.readRefNull()) {
98639878
return false;
@@ -11487,6 +11502,9 @@ bool BaseCompiler::emitBody() {
1148711502
emitComparison(emitCompareRef, ValType::AnyRef, Assembler::Equal));
1148811503
#endif
1148911504
#ifdef ENABLE_WASM_REFTYPES
11505+
case uint16_t(Op::RefFunc):
11506+
CHECK_NEXT(emitRefFunc());
11507+
break;
1149011508
case uint16_t(Op::RefNull):
1149111509
CHECK_NEXT(emitRefNull());
1149211510
break;

js/src/wasm/WasmBuiltins.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ const SymbolicAddressSignature SASigTableSet = {SymbolicAddress::TableSet,
172172
{_PTR, _I32, _RoN, _I32, _END}};
173173
const SymbolicAddressSignature SASigTableSize = {
174174
SymbolicAddress::TableSize, _I32, _Infallible, 2, {_PTR, _I32, _END}};
175+
const SymbolicAddressSignature SASigFuncRef = {
176+
SymbolicAddress::FuncRef, _RoN, _FailOnInvalidRef, 2, {_PTR, _I32, _END}};
175177
const SymbolicAddressSignature SASigPostBarrier = {
176178
SymbolicAddress::PostBarrier, _VOID, _Infallible, 2, {_PTR, _PTR, _END}};
177179
const SymbolicAddressSignature SASigPostBarrierFiltering = {
@@ -836,6 +838,9 @@ void* wasm::AddressOf(SymbolicAddress imm, ABIFunctionType* abiType) {
836838
case SymbolicAddress::TableSize:
837839
*abiType = Args_General2;
838840
return FuncCast(Instance::tableSize, *abiType);
841+
case SymbolicAddress::FuncRef:
842+
*abiType = Args_General2;
843+
return FuncCast(Instance::funcRef, *abiType);
839844
case SymbolicAddress::PostBarrier:
840845
*abiType = Args_General2;
841846
return FuncCast(Instance::postBarrier, *abiType);
@@ -957,6 +962,7 @@ bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) {
957962
case SymbolicAddress::TableInit:
958963
case SymbolicAddress::TableSet:
959964
case SymbolicAddress::TableSize:
965+
case SymbolicAddress::FuncRef:
960966
case SymbolicAddress::PostBarrier:
961967
case SymbolicAddress::PostBarrierFiltering:
962968
case SymbolicAddress::StructNew:

js/src/wasm/WasmBuiltins.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ extern const SymbolicAddressSignature SASigTableGrow;
6464
extern const SymbolicAddressSignature SASigTableInit;
6565
extern const SymbolicAddressSignature SASigTableSet;
6666
extern const SymbolicAddressSignature SASigTableSize;
67+
extern const SymbolicAddressSignature SASigFuncRef;
6768
extern const SymbolicAddressSignature SASigPostBarrier;
6869
extern const SymbolicAddressSignature SASigPostBarrierFiltering;
6970
extern const SymbolicAddressSignature SASigStructNew;

js/src/wasm/WasmFrameIter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,8 @@ static const char* ThunkedNativeToDescription(SymbolicAddress func) {
13761376
return "call to native table.set function";
13771377
case SymbolicAddress::TableSize:
13781378
return "call to native table.size function";
1379+
case SymbolicAddress::FuncRef:
1380+
return "call to native func.ref function";
13791381
case SymbolicAddress::PostBarrier:
13801382
return "call to native GC postbarrier (in wasm)";
13811383
case SymbolicAddress::PostBarrierFiltering:

js/src/wasm/WasmInstance.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,33 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
998998
return table.length();
999999
}
10001000

1001+
/* static */ void* Instance::funcRef(Instance* instance, uint32_t funcIndex) {
1002+
MOZ_ASSERT(SASigFuncRef.failureMode == FailureMode::FailOnInvalidRef);
1003+
JSContext* cx = TlsContext.get();
1004+
1005+
Tier tier = instance->code().bestTier();
1006+
const MetadataTier& metadataTier = instance->metadata(tier);
1007+
const FuncImportVector& funcImports = metadataTier.funcImports;
1008+
1009+
// If this is an import, we need to recover the original wrapper function to
1010+
// maintain referential equality between a re-exported function and
1011+
// 'ref.func'. The imported function object is stable across tiers, which is
1012+
// what we want.
1013+
if (funcIndex < funcImports.length()) {
1014+
FuncImportTls& import = instance->funcImportTls(funcImports[funcIndex]);
1015+
return AnyRef::fromJSObject(import.fun).forCompiledCode();
1016+
}
1017+
1018+
RootedFunction fun(cx);
1019+
RootedWasmInstanceObject instanceObj(cx, instance->object());
1020+
if (WasmInstanceObject::getExportedFunction(cx, instanceObj, funcIndex,
1021+
&fun)) {
1022+
return AnyRef::fromJSObject(fun).forCompiledCode();
1023+
}
1024+
1025+
return AnyRef::invalid().forCompiledCode();
1026+
}
1027+
10011028
/* static */ void Instance::postBarrier(Instance* instance,
10021029
gc::Cell** location) {
10031030
MOZ_ASSERT(SASigPostBarrier.failureMode == FailureMode::Infallible);

js/src/wasm/WasmInstance.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ class Instance {
216216
static int32_t tableInit(Instance* instance, uint32_t dstOffset,
217217
uint32_t srcOffset, uint32_t len, uint32_t segIndex,
218218
uint32_t tableIndex);
219+
static void* funcRef(Instance* instance, uint32_t funcIndex);
219220
static void postBarrier(Instance* instance, gc::Cell** location);
220221
static void postBarrierFiltering(Instance* instance, gc::Cell** location);
221222
static void* structNew(Instance* instance, uint32_t typeIndex);

js/src/wasm/WasmIonCompile.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3312,6 +3312,47 @@ static bool EmitTableSize(FunctionCompiler& f) {
33123312
#endif // ENABLE_WASM_REFTYPES
33133313

33143314
#ifdef ENABLE_WASM_REFTYPES
3315+
static bool EmitRefFunc(FunctionCompiler& f) {
3316+
uint32_t funcIndex;
3317+
if (!f.iter().readRefFunc(&funcIndex)) {
3318+
return false;
3319+
}
3320+
3321+
if (f.inDeadCode()) {
3322+
return true;
3323+
}
3324+
3325+
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
3326+
3327+
const SymbolicAddressSignature& callee = SASigFuncRef;
3328+
CallCompileState args;
3329+
if (!f.passInstance(callee.argTypes[0], &args)) {
3330+
return false;
3331+
}
3332+
3333+
MDefinition* funcIndexArg = f.constant(Int32Value(funcIndex), MIRType::Int32);
3334+
if (!funcIndexArg) {
3335+
return false;
3336+
}
3337+
if (!f.passArg(funcIndexArg, callee.argTypes[1], &args)) {
3338+
return false;
3339+
}
3340+
3341+
if (!f.finishCall(&args)) {
3342+
return false;
3343+
}
3344+
3345+
// The return value here is either null, denoting an error, or a short-lived
3346+
// pointer to a location containing a possibly-null ref.
3347+
MDefinition* ret;
3348+
if (!f.builtinInstanceMethodCall(callee, lineOrBytecode, args, &ret)) {
3349+
return false;
3350+
}
3351+
3352+
f.iter().setResult(ret);
3353+
return true;
3354+
}
3355+
33153356
static bool EmitRefNull(FunctionCompiler& f) {
33163357
if (!f.iter().readRefNull()) {
33173358
return false;
@@ -3788,6 +3829,8 @@ static bool EmitBodyExprs(FunctionCompiler& f) {
37883829
MCompare::Compare_RefOrNull));
37893830
#endif
37903831
#ifdef ENABLE_WASM_REFTYPES
3832+
case uint16_t(Op::RefFunc):
3833+
CHECK(EmitRefFunc(f));
37913834
case uint16_t(Op::RefNull):
37923835
CHECK(EmitRefNull(f));
37933836
case uint16_t(Op::RefIsNull):

js/src/wasm/WasmOpIter.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ class MOZ_STACK_CLASS OpIter : private Policy {
429429
MOZ_MUST_USE bool readI64Const(int64_t* i64);
430430
MOZ_MUST_USE bool readF32Const(float* f32);
431431
MOZ_MUST_USE bool readF64Const(double* f64);
432+
MOZ_MUST_USE bool readRefFunc(uint32_t* funcTypeIndex);
432433
MOZ_MUST_USE bool readRefNull();
433434
MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, ValueVector* argValues);
434435
MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex,
@@ -1472,6 +1473,22 @@ inline bool OpIter<Policy>::readF64Const(double* f64) {
14721473
return readFixedF64(f64) && push(ValType::F64);
14731474
}
14741475

1476+
template <typename Policy>
1477+
inline bool OpIter<Policy>::readRefFunc(uint32_t* funcTypeIndex) {
1478+
MOZ_ASSERT(Classify(op_) == OpKind::RefFunc);
1479+
1480+
if (!readVarU32(funcTypeIndex)) {
1481+
return fail("unable to read function index");
1482+
}
1483+
if (*funcTypeIndex >= env_.funcTypes.length()) {
1484+
return fail("function index out of range");
1485+
}
1486+
if (!env_.validForRefFunc.getBit(*funcTypeIndex)) {
1487+
return fail("function index is not in an element segment");
1488+
}
1489+
return push(StackType(ValType::FuncRef));
1490+
}
1491+
14751492
template <typename Policy>
14761493
inline bool OpIter<Policy>::readRefNull() {
14771494
MOZ_ASSERT(Classify(op_) == OpKind::RefNull);

0 commit comments

Comments
 (0)