Bug 1863794 - wasm: Generalize builtin module function support. r=yury

- Add optional result to builtin module functions
  - Allow RefType params to builtin module functions
  - Don't require linear memory to be available

These are required for expressing the kinds of functions that
we want for js-string-builtins.

Depends on D193110

Differential Revision: https://phabricator.services.mozilla.com/D193111
This commit is contained in:
Ryan Hunt 2023-11-30 00:46:31 +00:00
Родитель 7aa6f07799
Коммит 343cd58824
14 изменённых файлов: 203 добавлений и 118 удалений

Просмотреть файл

@ -2120,7 +2120,8 @@ static bool WasmBuiltinI8VecMul(JSContext* cx, unsigned argc, Value* vp) {
wasm::BuiltinModuleFuncId ids[] = {wasm::BuiltinModuleFuncId::I8VecMul};
Rooted<WasmModuleObject*> module(cx);
if (!wasm::CompileBuiltinModule(cx, ids, wasm::Shareable::False, &module)) {
if (!wasm::CompileBuiltinModule(cx, ids, Some(wasm::Shareable::False),
&module)) {
return false;
}
args.rval().set(ObjectValue(*module.get()));

Просмотреть файл

@ -56,6 +56,12 @@ def load_yaml(yaml_path):
return yaml.load(contents, OrderedLoader)
def cppBool(v):
if v:
return "true"
return "false"
def main(c_out, yaml_path):
data = load_yaml(yaml_path)
@ -66,21 +72,41 @@ def main(c_out, yaml_path):
sa = op["symbolic_address"]
contents += (
f" M({op['op']}, \"{op['export']}\", "
f"{sa['name']}, {sa['type']}, {op['entry']}, {i})\\\n"
f"{sa['name']}, {sa['type']}, {op['entry']}, {cppBool(op['uses_memory'])}, {i})\\\n"
)
contents += "\n"
for op in data:
# Define DECLARE_BUILTIN_MODULE_FUNC_SAS_PARAM_VALTYPES_<op> as:
# Define DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_<op> as:
# `{ValType::I32, ValType::I32, ...}`.
contents += (
f"#define DECLARE_BUILTIN_MODULE_FUNC_SAS_PARAM_VALTYPES_{op['op']} "
f"{{ValType::{', ValType::'.join(op['params'])}}}\n"
f"#define DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_{op['op']} "
f"{{{', '.join(op['params'])}}}\n"
)
# Define DECLARE_BUILTIN_MODULE_FUNC_PARAM_TYPES_<op> as:
# Define DECLARE_BUILTIN_MODULE_FUNC_PARAM_SASTYPES_<op> as:
# `<num_types>, {_PTR, _I32, ..., _PTR, _END}`.
sas_types = f"{{_PTR{''.join(', _' + p for p in op['params'])}, _PTR, _END}}"
num_types = len(op["params"]) + 2
contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_PARAM_TYPES_{op['op']} {num_types}, {sas_types}\n"
num_types = len(op["params"]) + 1
sas_types = (
f"{{_PTR{''.join(', ' + (p + '.toMIRType()') for p in op['params'])}"
)
if op["uses_memory"]:
sas_types += ", _PTR"
num_types += 1
sas_types += ", _END}"
contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_PARAM_SASTYPES_{op['op']} {num_types}, {sas_types}\n"
result_valtype = ""
result_sastype = ""
if "result" in op:
result_valtype = f"Some({op['result']})\n"
result_sastype = f"{op['result']}.toMIRType()\n"
else:
result_valtype = "Nothing()"
result_sastype = "_VOID"
contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_RESULT_VALTYPE_{op['op']} {result_valtype}\n"
contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_RESULT_SASTYPE_{op['op']} {result_sastype}\n"
contents += f"#define DECLARE_BUILTIN_MODULE_FUNC_FAILMODE_{op['op']} _{op['fail_mode']}\n"
generate_header(c_out, "wasm_WasmBuiltinModuleGenerated_h", contents)

Просмотреть файл

@ -9415,8 +9415,10 @@ bool BaseCompiler::emitCallBuiltinModuleFunc() {
return true;
}
// The final parameter of an builtinModuleFunc is implicitly the heap base
pushHeapBase(0);
if (builtinModuleFunc->usesMemory) {
// The final parameter of an builtinModuleFunc is implicitly the heap base
pushHeapBase(0);
}
// Call the builtinModuleFunc
return emitInstanceCall(builtinModuleFunc->signature);

Просмотреть файл

@ -32,14 +32,17 @@
using namespace js;
using namespace js::wasm;
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
static const ValType BuiltinModuleFunc##op##_Params[] = \
DECLARE_BUILTIN_MODULE_FUNC_SAS_PARAM_VALTYPES_##op; \
\
const BuiltinModuleFunc BuiltinModuleFunc##op = { \
export, \
mozilla::Span<const ValType>(BuiltinModuleFunc##op##_Params), \
SASig##sa_name, \
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, uses_memory, \
idx) \
static const ValType BuiltinModuleFunc##op##_Params[] = \
DECLARE_BUILTIN_MODULE_FUNC_PARAM_VALTYPES_##op; \
\
const BuiltinModuleFunc BuiltinModuleFunc##op = { \
export, \
mozilla::Span<const ValType>(BuiltinModuleFunc##op##_Params), \
DECLARE_BUILTIN_MODULE_FUNC_RESULT_VALTYPE_##op, \
SASig##sa_name, \
uses_memory, \
};
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
@ -50,15 +53,19 @@ bool BuiltinModuleFunc::funcType(FuncType* type) const {
if (!paramVec.append(params.data(), params.data() + params.size())) {
return false;
}
*type = FuncType(std::move(paramVec), ValTypeVector());
ValTypeVector resultVec;
if (result.isSome() && !resultVec.append(*result)) {
return false;
}
*type = FuncType(std::move(paramVec), std::move(resultVec));
return true;
}
/* static */
const BuiltinModuleFunc& BuiltinModuleFunc::getFromId(BuiltinModuleFuncId id) {
switch (id) {
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
case BuiltinModuleFuncId::op: \
#define VISIT_BUILTIN_FUNC(op, ...) \
case BuiltinModuleFuncId::op: \
return BuiltinModuleFunc##op;
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC
@ -89,7 +96,7 @@ bool EncodeFuncBody(const BuiltinModuleFunc& builtinModuleFunc,
bool wasm::CompileBuiltinModule(JSContext* cx,
const mozilla::Span<BuiltinModuleFuncId> ids,
Shareable sharedMemory,
mozilla::Maybe<Shareable> memory,
MutableHandle<WasmModuleObject*> result) {
// Create the options manually, enabling intrinsics
FeatureOptions featureOptions;
@ -113,23 +120,24 @@ bool wasm::CompileBuiltinModule(JSContext* cx,
return false;
}
// Add (import (memory 0))
CacheableName emptyString;
CacheableName memoryString;
if (!CacheableName::fromUTF8Chars("memory", &memoryString)) {
ReportOutOfMemory(cx);
return false;
}
if (!moduleEnv.imports.append(Import(std::move(emptyString),
std::move(memoryString),
DefinitionKind::Memory))) {
ReportOutOfMemory(cx);
return false;
}
if (!moduleEnv.memories.append(
MemoryDesc(Limits(0, Nothing(), sharedMemory)))) {
ReportOutOfMemory(cx);
return false;
if (memory.isSome()) {
// Add (import (memory 0))
CacheableName emptyString;
CacheableName memoryString;
if (!CacheableName::fromUTF8Chars("memory", &memoryString)) {
ReportOutOfMemory(cx);
return false;
}
if (!moduleEnv.imports.append(Import(std::move(emptyString),
std::move(memoryString),
DefinitionKind::Memory))) {
ReportOutOfMemory(cx);
return false;
}
if (!moduleEnv.memories.append(MemoryDesc(Limits(0, Nothing(), *memory)))) {
ReportOutOfMemory(cx);
return false;
}
}
// Add (type (func (params ...))) for each func. The function types will

Просмотреть файл

@ -19,6 +19,7 @@
#ifndef wasm_builtin_module_h
#define wasm_builtin_module_h
#include "mozilla/Maybe.h"
#include "mozilla/Span.h"
#include "wasm/WasmBuiltins.h"
@ -36,11 +37,15 @@ namespace wasm {
struct BuiltinModuleFunc {
// The name of the func as it is exported
const char* exportName;
// The params taken by the func. No results are required for these funcs
// at this time, so we omit them
// The params taken by the func.
mozilla::Span<const ValType> params;
// The optional result returned by the func.
mozilla::Maybe<const ValType> result;
// The signature of the builtin that implements the func
const SymbolicAddressSignature& signature;
// Whether this function takes a pointer to the memory base as a hidden final
// parameter.
bool usesMemory;
// Allocate a FuncType for this func, returning false for OOM
bool funcType(FuncType* type) const;
@ -53,7 +58,7 @@ struct BuiltinModuleFunc {
// Compile and return the builtin module for a given set of operations.
bool CompileBuiltinModule(JSContext* cx,
const mozilla::Span<BuiltinModuleFuncId> ids,
Shareable sharedMemory,
mozilla::Maybe<Shareable> memory,
MutableHandle<WasmModuleObject*> result);
} // namespace wasm

Просмотреть файл

@ -12,10 +12,12 @@
entry: Instance::intrI8VecMul
export: i8vecmul
params:
- I32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
#if defined(ENABLE_WASM_MOZ_INTGEMM)
@ -38,12 +40,14 @@
entry: intgemm::IntrI8PrepareB
export: int8_prepare_b
params:
- I32
- F32
- F32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
# Prepare B for the Matrix Multiply intrinsic from transposed version of Input matrix B.
@ -61,12 +65,14 @@
entry: intgemm::IntrI8PrepareBFromTransposed
export: int8_prepare_b_from_transposed
params:
- I32
- F32
- F32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
# Prepare B for the Matrix Multiply intrinsic from a quantized and transposed version of Input
@ -84,10 +90,12 @@
entry: intgemm::IntrI8PrepareBFromQuantizedTransposed
export: int8_prepare_b_from_quantized_transposed
params:
- I32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
# Prepare A for the Matrix Multiply intrinsic from Input matrix A.
@ -108,12 +116,14 @@
entry: intgemm::IntrI8PrepareA
export: int8_prepare_a
params:
- I32
- F32
- F32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
# Prepares bias for the Matrix Multiply intrinsic.
@ -132,15 +142,17 @@
entry: intgemm::IntrI8PrepareBias
export: int8_prepare_bias
params:
- I32
- F32
- F32
- F32
- F32
- I32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
# Perform multiplication of 2 matrices followed by adding a bias.
@ -165,18 +177,20 @@
entry: intgemm::IntrI8MultiplyAndAddBias
export: int8_multiply_and_add_bias
params:
- I32
- F32
- F32
- I32
- F32
- F32
- I32
- F32
- I32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::f32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
# Select a subset of columns of prepared B.
@ -192,11 +206,13 @@
entry: intgemm::IntrI8SelectColumnsOfB
export: int8_select_columns_of_b
params:
- I32
- I32
- I32
- I32
- I32
- I32
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
- 'ValType::i32()'
fail_mode: FailOnNegI32
uses_memory: true
#endif // ENABLE_WASM_MOZ_INTGEMM

Просмотреть файл

@ -393,10 +393,12 @@ const SymbolicAddressSignature SASigArrayCopy = {
7,
{_PTR, _RoN, _I32, _RoN, _I32, _I32, _I32, _END}};
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
const SymbolicAddressSignature SASig##sa_name = { \
SymbolicAddress::sa_name, _VOID, _FailOnNegI32, \
DECLARE_BUILTIN_MODULE_FUNC_PARAM_TYPES_##op};
#define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) \
const SymbolicAddressSignature SASig##sa_name = { \
SymbolicAddress::sa_name, \
DECLARE_BUILTIN_MODULE_FUNC_RESULT_SASTYPE_##op, \
DECLARE_BUILTIN_MODULE_FUNC_FAILMODE_##op, \
DECLARE_BUILTIN_MODULE_FUNC_PARAM_SASTYPES_##op};
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC
@ -1455,7 +1457,7 @@ void* wasm::AddressOf(SymbolicAddress imm, ABIFunctionType* abiType) {
*abiType = Args_General1;
return FuncCast(PrintText, *abiType);
#endif
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, ...) \
case SymbolicAddress::sa_name: \
*abiType = abitype; \
return FuncCast(entry, *abiType);
@ -1617,7 +1619,7 @@ bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) {
case SymbolicAddress::ArrayInitData:
case SymbolicAddress::ArrayInitElem:
case SymbolicAddress::ArrayCopy:
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
#define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) \
case SymbolicAddress::sa_name:
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC

Просмотреть файл

@ -142,7 +142,7 @@ enum class SymbolicAddress {
ArrayInitData,
ArrayInitElem,
ArrayCopy,
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) sa_name,
#define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) sa_name,
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC
#ifdef WASM_CODEGEN_DEBUG
@ -278,7 +278,7 @@ extern const SymbolicAddressSignature SASigArrayNewElem;
extern const SymbolicAddressSignature SASigArrayInitData;
extern const SymbolicAddressSignature SASigArrayInitElem;
extern const SymbolicAddressSignature SASigArrayCopy;
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
#define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) \
extern const SymbolicAddressSignature SASig##sa_name;
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC

Просмотреть файл

@ -956,7 +956,8 @@ enum class BuiltinModuleFuncId {
// emitted internally when compiling intrinsic modules and are rejected by wasm
// validation.
// See wasm/WasmBuiltinModule.yaml for the list.
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, has_memory, \
idx) \
op = idx, // NOLINT
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC

Просмотреть файл

@ -1794,8 +1794,8 @@ static const char* ThunkedNativeToDescription(SymbolicAddress func) {
return "call to native array.init_elem function";
case SymbolicAddress::ArrayCopy:
return "call to native array.copy function";
#define VISIT_BUILTIN_FUNC(op, export, sa_name, abitype, entry, idx) \
case SymbolicAddress::sa_name: \
#define VISIT_BUILTIN_FUNC(op, export, sa_name, ...) \
case SymbolicAddress::sa_name: \
return "call to native " #op " builtin (in wasm)";
FOR_EACH_BUILTIN_MODULE_FUNC(VISIT_BUILTIN_FUNC)
#undef VISIT_BUILTIN_FUNC

Просмотреть файл

@ -7708,16 +7708,29 @@ static bool EmitCallBuiltinModuleFunc(FunctionCompiler& f) {
return false;
}
MDefinition* memoryBase = f.memoryBase(0);
if (!f.passArg(memoryBase, MIRType::Pointer, &args)) {
return false;
if (builtinModuleFunc->usesMemory) {
MDefinition* memoryBase = f.memoryBase(0);
if (!f.passArg(memoryBase, MIRType::Pointer, &args)) {
return false;
}
}
if (!f.finishCall(&args)) {
return false;
}
return f.builtinInstanceMethodCall(callee, bytecodeOffset, args);
bool hasResult = builtinModuleFunc->result.isSome();
MDefinition* result = nullptr;
MDefinition** resultOutParam = hasResult ? &result : nullptr;
if (!f.builtinInstanceMethodCall(callee, bytecodeOffset, args,
resultOutParam)) {
return false;
}
if (hasResult) {
f.iter().setResult(result);
}
return true;
}
static bool EmitBodyExprs(FunctionCompiler& f) {

Просмотреть файл

@ -5158,7 +5158,7 @@ static bool WebAssembly_mozIntGemm(JSContext* cx, unsigned argc, Value* vp) {
wasm::BuiltinModuleFuncId::I8PrepareBias,
wasm::BuiltinModuleFuncId::I8MultiplyAndAddBias,
wasm::BuiltinModuleFuncId::I8SelectColumnsOfB};
if (!wasm::CompileBuiltinModule(cx, ids, Shareable::False, &module)) {
if (!wasm::CompileBuiltinModule(cx, ids, Some(Shareable::False), &module)) {
ReportOutOfMemory(cx);
return false;
}

Просмотреть файл

@ -4097,10 +4097,16 @@ inline bool OpIter<Policy>::readCallBuiltinModuleFunc(
*builtinModuleFunc = &BuiltinModuleFunc::getFromId(BuiltinModuleFuncId(id));
if (env_.numMemories() == 0) {
if ((*builtinModuleFunc)->usesMemory && env_.numMemories() == 0) {
return fail("can't touch memory without memory");
}
return popWithTypes((*builtinModuleFunc)->params, params);
if (!popWithTypes((*builtinModuleFunc)->params, params)) {
return false;
}
if ((*builtinModuleFunc)->result.isNothing()) {
return true;
}
return push(*(*builtinModuleFunc)->result);
}
} // namespace wasm

Просмотреть файл

@ -112,11 +112,11 @@ union PackedTypeCode {
static PackedTypeCode pack(TypeCode tc) { return pack(tc, nullptr, false); }
bool isValid() const { return typeCode_ != NoTypeCode; }
constexpr bool isValid() const { return typeCode_ != NoTypeCode; }
PackedRepr bits() const { return bits_; }
TypeCode typeCode() const {
constexpr TypeCode typeCode() const {
MOZ_ASSERT(isValid());
return TypeCode(typeCode_);
}
@ -134,7 +134,7 @@ union PackedTypeCode {
// what ValType needs, so that this decoding step is not necessary, but that
// moves complexity elsewhere, and the perf gain here would be only about 1%
// for baseline compilation throughput.
TypeCode typeCodeAbstracted() const {
constexpr TypeCode typeCodeAbstracted() const {
TypeCode tc = typeCode();
return tc < LowestPrimitiveTypeCode ? AbstractReferenceTypeCode : tc;
}
@ -634,6 +634,11 @@ class PackedType : public T {
inline void AddRef() const;
inline void Release() const;
static constexpr PackedType i32() { return PackedType(PackedType::I32); }
static constexpr PackedType f32() { return PackedType(PackedType::F32); }
static constexpr PackedType i64() { return PackedType(PackedType::I64); }
static constexpr PackedType f64() { return PackedType(PackedType::F64); }
static PackedType fromMIRType(jit::MIRType mty) {
switch (mty) {
case jit::MIRType::Int32:
@ -830,7 +835,7 @@ class PackedType : public T {
// as a pointer. At the JS/wasm boundary, an AnyRef can be represented as a
// JS::Value, and the type translation may have to be handled specially and on
// a case-by-case basis.
jit::MIRType toMIRType() const {
constexpr jit::MIRType toMIRType() const {
switch (tc_.typeCodeAbstracted()) {
case TypeCode::I32:
return jit::MIRType::Int32;