зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changeset d737bf8f1ca2 (bug 1692462) for bustages in check_vanilla_allocations.py. CLOSED TREE
This commit is contained in:
Родитель
65745dbb4a
Коммит
85f0d3073f
|
@ -23,7 +23,6 @@
|
|||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
@ -1256,79 +1255,6 @@ static bool DisassembleNative(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool ComputeTier(JSContext* cx, const wasm::Code& code,
|
||||
HandleValue tierSelection, wasm::Tier* tier) {
|
||||
*tier = code.stableTier();
|
||||
if (!tierSelection.isUndefined() &&
|
||||
!ConvertToTier(cx, tierSelection, code, tier)) {
|
||||
JS_ReportErrorASCII(cx, "invalid tier");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!code.hasTier(*tier)) {
|
||||
JS_ReportErrorASCII(cx, "function missing selected tier");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DisassembleIt(
|
||||
JSContext* cx, bool asString, MutableHandleValue rval,
|
||||
const std::function<void(void (*)(const char* text))>& disassembleIt) {
|
||||
if (asString) {
|
||||
DisasmBuffer buf(cx);
|
||||
disasmBuf.set(&buf);
|
||||
auto onFinish = mozilla::MakeScopeExit([&] { disasmBuf.set(nullptr); });
|
||||
disassembleIt(captureDisasmText);
|
||||
if (buf.oom) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
JSString* sresult = buf.builder.finishString();
|
||||
if (!sresult) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
rval.setString(sresult);
|
||||
return true;
|
||||
}
|
||||
|
||||
disassembleIt([](const char* text) { fprintf(stderr, "%s\n", text); });
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WasmDisassembleFunction(JSContext* cx, const HandleFunction& func,
|
||||
HandleValue tierSelection, bool asString,
|
||||
MutableHandleValue rval) {
|
||||
wasm::Instance& instance = wasm::ExportedFunctionToInstance(func);
|
||||
uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(func);
|
||||
wasm::Tier tier;
|
||||
|
||||
if (!ComputeTier(cx, instance.code(), tierSelection, &tier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DisassembleIt(
|
||||
cx, asString, rval, [&](void (*captureText)(const char*)) {
|
||||
instance.disassembleExport(cx, funcIndex, tier, captureText);
|
||||
});
|
||||
}
|
||||
|
||||
static bool WasmDisassembleCode(JSContext* cx, const wasm::Code& code,
|
||||
HandleValue tierSelection, int kindSelection,
|
||||
bool asString, MutableHandleValue rval) {
|
||||
wasm::Tier tier;
|
||||
if (!ComputeTier(cx, code, tierSelection, &tier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DisassembleIt(cx, asString, rval,
|
||||
[&](void (*captureText)(const char*)) {
|
||||
code.disassemble(cx, tier, kindSelection, captureText);
|
||||
});
|
||||
}
|
||||
|
||||
static bool WasmDisassemble(JSContext* cx, unsigned argc, Value* vp) {
|
||||
if (!cx->options().wasm()) {
|
||||
JS_ReportErrorASCII(cx, "wasm support unavailable");
|
||||
|
@ -1344,87 +1270,51 @@ static bool WasmDisassemble(JSContext* cx, unsigned argc, Value* vp) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool asString = false;
|
||||
RootedValue tierSelection(cx);
|
||||
int kindSelection = (1 << wasm::CodeRange::Function);
|
||||
if (args.length() > 1 && args[1].isObject()) {
|
||||
RootedObject options(cx, &args[1].toObject());
|
||||
RootedValue val(cx);
|
||||
|
||||
if (!JS_GetProperty(cx, options, "asString", &val)) {
|
||||
return false;
|
||||
}
|
||||
asString = val.isBoolean() && val.toBoolean();
|
||||
|
||||
if (!JS_GetProperty(cx, options, "tier", &tierSelection)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JS_GetProperty(cx, options, "kinds", &val)) {
|
||||
return false;
|
||||
}
|
||||
if (val.isString() && val.toString()->hasLatin1Chars()) {
|
||||
AutoStableStringChars stable(cx);
|
||||
if (!stable.init(cx, val.toString())) {
|
||||
return false;
|
||||
}
|
||||
const char* p = (const char*)(stable.latin1Chars());
|
||||
const char* end = p + val.toString()->length();
|
||||
int selection = 0;
|
||||
for (;;) {
|
||||
if (strncmp(p, "Function", 8) == 0) {
|
||||
selection |= (1 << wasm::CodeRange::Function);
|
||||
p += 8;
|
||||
} else if (strncmp(p, "InterpEntry", 11) == 0) {
|
||||
selection |= (1 << wasm::CodeRange::InterpEntry);
|
||||
p += 11;
|
||||
} else if (strncmp(p, "JitEntry", 8) == 0) {
|
||||
selection |= (1 << wasm::CodeRange::JitEntry);
|
||||
p += 8;
|
||||
} else if (strncmp(p, "ImportInterpExit", 16) == 0) {
|
||||
selection |= (1 << wasm::CodeRange::ImportInterpExit);
|
||||
p += 16;
|
||||
} else if (strncmp(p, "ImportJitExit", 13) == 0) {
|
||||
selection |= (1 << wasm::CodeRange::ImportJitExit);
|
||||
p += 13;
|
||||
} else if (strncmp(p, "all", 3) == 0) {
|
||||
selection = ~0;
|
||||
p += 3;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (p == end || *p != ',') {
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
if (p == end) {
|
||||
kindSelection = selection;
|
||||
} else {
|
||||
JS_ReportErrorASCII(cx, "argument object has invalid `kinds`");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RootedFunction func(cx, args[0].toObject().maybeUnwrapIf<JSFunction>());
|
||||
if (func && wasm::IsWasmExportedFunction(func)) {
|
||||
return WasmDisassembleFunction(cx, func, tierSelection, asString,
|
||||
args.rval());
|
||||
|
||||
if (!func || !wasm::IsWasmExportedFunction(func)) {
|
||||
JS_ReportErrorASCII(cx, "argument is not an exported wasm function");
|
||||
return false;
|
||||
}
|
||||
if (args[0].toObject().is<WasmModuleObject>()) {
|
||||
return WasmDisassembleCode(
|
||||
cx, args[0].toObject().as<WasmModuleObject>().module().code(),
|
||||
tierSelection, kindSelection, asString, args.rval());
|
||||
|
||||
wasm::Instance& instance = wasm::ExportedFunctionToInstance(func);
|
||||
uint32_t funcIndex = wasm::ExportedFunctionToFuncIndex(func);
|
||||
|
||||
wasm::Tier tier = instance.code().stableTier();
|
||||
|
||||
if (args.length() > 1 &&
|
||||
!ConvertToTier(cx, args[1], instance.code(), &tier)) {
|
||||
JS_ReportErrorASCII(cx, "invalid tier");
|
||||
return false;
|
||||
}
|
||||
if (args[0].toObject().is<WasmInstanceObject>()) {
|
||||
return WasmDisassembleCode(
|
||||
cx, args[0].toObject().as<WasmInstanceObject>().instance().code(),
|
||||
tierSelection, kindSelection, asString, args.rval());
|
||||
|
||||
if (!instance.code().hasTier(tier)) {
|
||||
JS_ReportErrorASCII(cx, "function missing selected tier");
|
||||
return false;
|
||||
}
|
||||
JS_ReportErrorASCII(
|
||||
cx, "argument is not an exported wasm function or a wasm module");
|
||||
return false;
|
||||
|
||||
if (args.length() > 2 && args[2].isBoolean() && args[2].toBoolean()) {
|
||||
DisasmBuffer buf(cx);
|
||||
disasmBuf.set(&buf);
|
||||
auto onFinish = mozilla::MakeScopeExit([&] { disasmBuf.set(nullptr); });
|
||||
instance.disassembleExport(cx, funcIndex, tier, captureDisasmText);
|
||||
if (buf.oom) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
JSString* sresult = buf.builder.finishString();
|
||||
if (!sresult) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
args.rval().setString(sresult);
|
||||
return true;
|
||||
}
|
||||
|
||||
instance.disassembleExport(cx, funcIndex, tier, [](const char* text) {
|
||||
fprintf(stderr, "%s\n", text);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class Flag { Tier2Complete, Deserialized };
|
||||
|
@ -6989,22 +6879,11 @@ gc::ZealModeHelpText),
|
|||
" until background compilation is complete."),
|
||||
|
||||
JS_FN_HELP("wasmDis", WasmDisassemble, 1, 0,
|
||||
"wasmDis(wasmObject[, options])\n",
|
||||
" Disassembles generated machine code from an exported WebAssembly function,\n"
|
||||
" or from all the functions defined in the module or instance, exported and not.\n"
|
||||
" The `options` is an object with the following optional keys:\n"
|
||||
" asString: boolean - if true, return a string rather than printing on stderr,\n"
|
||||
" the default is false.\n"
|
||||
" tier: string - one of 'stable', 'best', 'baseline', or 'ion'; the default is\n"
|
||||
" 'stable'.\n"
|
||||
" kinds: string - if set, and the wasmObject is a module or instance, a\n"
|
||||
" comma-separated list of the following keys, the default is `Function`:\n"
|
||||
" Function - functions defined in the module\n"
|
||||
" InterpEntry - C++-to-wasm stubs\n"
|
||||
" JitEntry - jitted-js-to-wasm stubs\n"
|
||||
" ImportInterpExit - wasm-to-C++ stubs\n"
|
||||
" ImportJitExit - wasm-to-jitted-JS stubs\n"
|
||||
" all - all kinds, including obscure ones\n"),
|
||||
"wasmDis(function[, tier [, asString]])",
|
||||
" Disassembles generated machine code from an exported WebAssembly function.\n"
|
||||
" The tier is a string, 'stable', 'best', 'baseline', or 'ion'; the default is\n"
|
||||
" 'stable'. If `asString` is present and is the value `true` then the output\n"
|
||||
" is returned as a string; otherwise it is printed on stderr."),
|
||||
|
||||
JS_FN_HELP("wasmHasTier2CompilationCompleted", WasmHasTier2CompilationCompleted, 1, 0,
|
||||
"wasmHasTier2CompilationCompleted(module)",
|
||||
|
|
|
@ -147,7 +147,7 @@ function codegenTestX64_adhoc(module_text, export_name, expected, options = {})
|
|||
let ins = wasmEvalText(module_text, {}, options.features);
|
||||
if (options.instanceBox)
|
||||
options.instanceBox.value = ins;
|
||||
let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true});
|
||||
let output = wasmDis(ins.exports[export_name], "ion", true);
|
||||
if (!options.no_prefix)
|
||||
expected = x64_prefix + '\n' + expected;
|
||||
if (!options.no_suffix)
|
||||
|
|
|
@ -49,7 +49,7 @@ function codegenTestX86_adhoc(module_text, export_name, expected, options = {})
|
|||
assertEq(hasDisassembler(), true);
|
||||
|
||||
let ins = wasmEvalText(module_text);
|
||||
let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true});
|
||||
let output = wasmDis(ins.exports[export_name], "ion", true);
|
||||
if (!options.no_prefix)
|
||||
expected = x86_prefix + '\n' + expected;
|
||||
if (!options.no_suffix)
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
// |jit-test| skip-if: !hasDisassembler()
|
||||
|
||||
// Test that the disassembler is reasonably sane.
|
||||
|
||||
var mod = new WebAssembly.Module(wasmTextToBinary(`
|
||||
(module
|
||||
(func $hum (import "m" "hum") (param i32) (result f64))
|
||||
(memory 1)
|
||||
(func $hi (export "f") (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32)
|
||||
(i32.add (i32.load (local.get 5)) (i32.load (local.get 6))))
|
||||
(func $ho (param i32) (result i32) (i32.const 37))
|
||||
)
|
||||
`));
|
||||
|
||||
// The following capture the disassembly as a string. We can't really check
|
||||
// that no other output is produced.
|
||||
|
||||
var s = wasmDis(mod, {tier:'best', asString:true});
|
||||
assertEq(typeof s, "string")
|
||||
assertEq(s.match(/Kind = Function/g).length, 3)
|
||||
|
||||
var ins = new WebAssembly.Instance(mod, {m:{hum:(x) => x+0.5}});
|
||||
var s = wasmDis(ins, {tier:'best', asString:true});
|
||||
assertEq(typeof s, "string")
|
||||
assertEq(s.match(/Kind = Function/g).length, 3)
|
||||
|
||||
var s = wasmDis(ins.exports.f, {tier:'best', asString:true})
|
||||
assertEq(typeof s, "string")
|
||||
|
||||
var s = wasmDis(ins, {asString:true, kinds:"InterpEntry,ImportInterpExit,Function"})
|
||||
assertEq(typeof s, "string")
|
||||
assertEq(s.match(/Kind = Function/g).length, 3)
|
||||
assertEq(s.match(/Kind = InterpEntry/g).length, 1)
|
||||
assertEq(s.match(/Kind = ImportInterpExit/g).length, 1)
|
||||
assertEq(s.match(/name = hi/g).length, 2)
|
||||
assertEq(s.match(/name = ho/g).length, 1)
|
||||
assertEq(s.match(/name = hum/g).length, 2)
|
||||
|
||||
// This one prints to stderr, we can't check the output but we can check that a
|
||||
// string is not returned.
|
||||
|
||||
var s = wasmDis(ins, {tier:'best'})
|
||||
assertEq(typeof s, "undefined")
|
|
@ -86,7 +86,7 @@ ${suffix}
|
|||
(v128.store (i32.const 0) (call $f)))
|
||||
(func $f (export "f") (result v128)
|
||||
(v128.const ${bits})))`);
|
||||
let output = wasmDis(ins.exports.f, {tier:"baseline", asString:true});
|
||||
let output = wasmDis(ins.exports.f, "baseline", true);
|
||||
assertEq(output.match(new RegExp(expected)) != null, true);
|
||||
let mem = new Int8Array(ins.exports.mem.buffer);
|
||||
set(mem, 0, iota(16).map(x => -1-x));
|
||||
|
|
|
@ -23,16 +23,16 @@ var ins = wasmEvalText(`
|
|||
|
||||
switch (wasmCompileMode()) {
|
||||
case "ion":
|
||||
assertEq(wasmDis(ins.exports.wasm2wasm, {tier:'stable', asString:true}).match(/call.*\n.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasm2import, {tier:'stable', asString:true}).match(/call.*\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasmIndirect, {tier:'stable', asString:true}).match(/call.*\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.instanceCall, {tier:'stable', asString:true}).match(/call.*\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasm2wasm, 'stable', true).match(/call.*\n.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasm2import, 'stable', true).match(/call.*\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasmIndirect, 'stable', true).match(/call.*\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.instanceCall, 'stable', true).match(/call.*\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
break;
|
||||
case "baseline":
|
||||
assertEq(wasmDis(ins.exports.wasm2wasm, {tier:'stable', asString:true}).match(/call.*\n.*add.*%rsp\n.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasm2import, {tier:'stable', asString:true}).match(/call.*\n.*add.*%rsp\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasmIndirect, {tier:'stable', asString:true}).match(/call.*\n.*add.*%rsp\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.instanceCall, {tier:'stable', asString:true}).match(/call.*\n.*add.*%rsp\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasm2wasm, 'stable', true).match(/call.*\n.*add.*%rsp\n.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasm2import, 'stable', true).match(/call.*\n.*add.*%rsp\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.wasmIndirect, 'stable', true).match(/call.*\n.*add.*%rsp\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
assertEq(wasmDis(ins.exports.instanceCall, 'stable', true).match(/call.*\n.*add.*%rsp\n(?:.*movq.*\n)*.*mov %eax, %eax/).length, 1);
|
||||
break;
|
||||
default:
|
||||
throw "Unexpected compile mode";
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#include "jsnum.h"
|
||||
|
||||
#include "jit/Disassemble.h"
|
||||
#include "jit/ExecutableAllocator.h"
|
||||
#ifdef JS_ION_PERF
|
||||
# include "jit/PerfSpewer.h"
|
||||
|
@ -1537,65 +1536,6 @@ uint8_t* Code::serialize(uint8_t* cursor, const LinkData& linkData) const {
|
|||
return cursor;
|
||||
}
|
||||
|
||||
void Code::disassemble(JSContext* cx, Tier tier, int kindSelection,
|
||||
PrintCallback printString) const {
|
||||
const MetadataTier& metadataTier = metadata(tier);
|
||||
const CodeTier& codeTier = this->codeTier(tier);
|
||||
const ModuleSegment& segment = codeTier.segment();
|
||||
|
||||
for (const CodeRange& range : metadataTier.codeRanges) {
|
||||
if (kindSelection & (1 << range.kind())) {
|
||||
MOZ_ASSERT(range.begin() < segment.length());
|
||||
MOZ_ASSERT(range.end() < segment.length());
|
||||
|
||||
const char* kind;
|
||||
char kindbuf[128];
|
||||
switch (range.kind()) {
|
||||
case CodeRange::Function:
|
||||
kind = "Function";
|
||||
break;
|
||||
case CodeRange::InterpEntry:
|
||||
kind = "InterpEntry";
|
||||
break;
|
||||
case CodeRange::JitEntry:
|
||||
kind = "JitEntry";
|
||||
break;
|
||||
case CodeRange::ImportInterpExit:
|
||||
kind = "ImportInterpExit";
|
||||
break;
|
||||
case CodeRange::ImportJitExit:
|
||||
kind = "ImportJitExit";
|
||||
break;
|
||||
default:
|
||||
SprintfLiteral(kindbuf, "CodeRange::Kind(%d)", range.kind());
|
||||
kind = kindbuf;
|
||||
break;
|
||||
}
|
||||
const char* separator =
|
||||
"\n--------------------------------------------------\n";
|
||||
// The buffer is quite large in order to accomodate mangled C++ names;
|
||||
// lengths over 3500 have been observed in the wild.
|
||||
char buf[4096];
|
||||
if (range.hasFuncIndex()) {
|
||||
const char* funcName = "(unknown)";
|
||||
UTF8Bytes namebuf;
|
||||
if (metadata().getFuncNameStandalone(range.funcIndex(), &namebuf) &&
|
||||
namebuf.append('\0')) {
|
||||
funcName = namebuf.begin();
|
||||
}
|
||||
SprintfLiteral(buf, "%sKind = %s, index = %d, name = %s:\n", separator,
|
||||
kind, range.funcIndex(), funcName);
|
||||
} else {
|
||||
SprintfLiteral(buf, "%sKind = %s\n", separator, kind);
|
||||
}
|
||||
printString(buf);
|
||||
|
||||
uint8_t* theCode = segment.base() + range.begin();
|
||||
jit::Disassemble(theCode, range.end() - range.begin(), printString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wasm::PatchDebugSymbolicAccesses(uint8_t* codeBase, MacroAssembler& masm) {
|
||||
#ifdef WASM_CODEGEN_DEBUG
|
||||
for (auto& access : masm.symbolicAccesses()) {
|
||||
|
|
|
@ -748,11 +748,6 @@ class Code : public ShareableBase<Code> {
|
|||
void ensureProfilingLabels(bool profilingEnabled) const;
|
||||
const char* profilingLabel(uint32_t funcIndex) const;
|
||||
|
||||
// Wasm disassembly support
|
||||
|
||||
void disassemble(JSContext* cx, Tier tier, int kindSelection,
|
||||
PrintCallback printString) const;
|
||||
|
||||
// about:memory reporting:
|
||||
|
||||
void addSizeOfMiscIfNotSeen(MallocSizeOf mallocSizeOf,
|
||||
|
|
|
@ -2123,7 +2123,7 @@ void Instance::destroyBreakpointSite(JSFreeOp* fop, uint32_t offset) {
|
|||
}
|
||||
|
||||
void Instance::disassembleExport(JSContext* cx, uint32_t funcIndex, Tier tier,
|
||||
PrintCallback printString) const {
|
||||
PrintCallback callback) const {
|
||||
const MetadataTier& metadataTier = metadata(tier);
|
||||
const FuncExport& funcExport = metadataTier.lookupFuncExport(funcIndex);
|
||||
const CodeRange& range = metadataTier.codeRange(funcExport);
|
||||
|
@ -2134,7 +2134,7 @@ void Instance::disassembleExport(JSContext* cx, uint32_t funcIndex, Tier tier,
|
|||
MOZ_ASSERT(range.end() < segment.length());
|
||||
|
||||
uint8_t* functionCode = segment.base() + range.begin();
|
||||
jit::Disassemble(functionCode, range.end() - range.begin(), printString);
|
||||
jit::Disassemble(functionCode, range.end() - range.begin(), callback);
|
||||
}
|
||||
|
||||
void Instance::addSizeOfMisc(MallocSizeOf mallocSizeOf,
|
||||
|
|
|
@ -175,7 +175,7 @@ class Instance {
|
|||
// Wasm disassembly support
|
||||
|
||||
void disassembleExport(JSContext* cx, uint32_t funcIndex, Tier tier,
|
||||
PrintCallback printString) const;
|
||||
PrintCallback callback) const;
|
||||
|
||||
public:
|
||||
// Functions to be called directly from wasm code.
|
||||
|
|
Загрузка…
Ссылка в новой задаче