Bug 1445272: Implement basic anyref support in the baseline compiler; r=lth

--HG--
extra : rebase_source : b445cf2adf366f32022892d21241b27b8404eb91
This commit is contained in:
Benjamin Bouvier 2018-04-11 19:03:04 +02:00
Родитель a45951f2cd
Коммит 43a88914d0
5 изменённых файлов: 778 добавлений и 28 удалений

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

@ -0,0 +1,383 @@
// Ensure that if gc types aren't enabled, test cases properly fail.
if (!wasmGcEnabled()) {
quit(0);
}
// Dummy constructor.
function Baguette(calories) {
this.calories = calories;
}
// Type checking.
const { validate, CompileError } = WebAssembly;
assertErrorMessage(() => wasmEvalText(`(module
(func (result anyref)
i32.const 42
)
)`), CompileError, mismatchError('i32', 'anyref'));
assertErrorMessage(() => wasmEvalText(`(module
(func (result anyref)
i32.const 0
ref.null anyref
i32.const 42
select
)
)`), CompileError, /select operand types/);
assertErrorMessage(() => wasmEvalText(`(module
(func (result i32)
ref.null anyref
if
i32.const 42
end
)
)`), CompileError, mismatchError('anyref', 'i32'));
// Basic compilation tests.
let simpleTests = [
"(module (func (drop (ref.null anyref))))",
"(module (func $test (local anyref)))",
"(module (func $test (param anyref)))",
"(module (func $test (result anyref) (ref.null anyref)))",
"(module (func $test (block anyref (unreachable)) unreachable))",
"(module (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
`(module (import "a" "b" (param anyref)))`,
`(module (import "a" "b" (result anyref)))`,
];
for (let src of simpleTests) {
wasmEvalText(src, {a:{b(){}}});
assertEq(validate(wasmTextToBinary(src)), true);
}
// Basic behavioral tests.
let { exports } = wasmEvalText(`(module
(func (export "is_null") (result i32)
ref.null anyref
ref.is_null
)
(func $sum (result i32) (param i32)
get_local 0
i32.const 42
i32.add
)
(func (export "is_null_spill") (result i32)
ref.null anyref
i32.const 58
call $sum
drop
ref.is_null
)
(func (export "is_null_local") (result i32) (local anyref)
ref.null anyref
set_local 0
i32.const 58
call $sum
drop
get_local 0
ref.is_null
)
)`);
assertEq(exports.is_null(), 1);
assertEq(exports.is_null_spill(), 1);
assertEq(exports.is_null_local(), 1);
// Anyref param and result in wasm functions.
exports = wasmEvalText(`(module
(func (export "is_null") (result i32) (param $ref anyref)
get_local $ref
ref.is_null
)
(func (export "ref_or_null") (result anyref) (param $ref anyref) (param $selector i32)
get_local $ref
ref.null anyref
get_local $selector
select
)
(func $recursive (export "nested") (result anyref) (param $ref anyref) (param $i i32)
;; i == 10 => ret $ref
get_local $i
i32.const 10
i32.eq
if
get_local $ref
return
end
get_local $ref
get_local $i
i32.const 1
i32.add
call $recursive
)
)`).exports;
assertErrorMessage(() => exports.is_null(undefined), TypeError, "can't convert undefined to object");
assertEq(exports.is_null(null), 1);
assertEq(exports.is_null({}), 0);
assertEq(exports.is_null("hi"), 0);
assertEq(exports.is_null(3), 0);
assertEq(exports.is_null(3.5), 0);
assertEq(exports.is_null(true), 0);
assertEq(exports.is_null(Symbol("croissant")), 0);
assertEq(exports.is_null(new Baguette(100)), 0);
let baguette = new Baguette(42);
assertEq(exports.ref_or_null(null, 0), null);
assertEq(exports.ref_or_null(baguette, 0), null);
let ref = exports.ref_or_null(baguette, 1);
assertEq(ref, baguette);
assertEq(ref.calories, baguette.calories);
ref = exports.nested(baguette, 0);
assertEq(ref, baguette);
assertEq(ref.calories, baguette.calories);
// More interesting use cases about control flow joins.
function assertJoin(body) {
let val = { i: -1 };
assertEq(wasmEvalText(`(module
(func (export "test") (param $ref anyref) (param $i i32) (result anyref)
${body}
)
)`).exports.test(val), val);
assertEq(val.i, -1);
}
assertJoin("(block anyref get_local $ref)");
assertJoin("(block $out anyref get_local $ref br $out)");
assertJoin("(loop anyref get_local $ref)");
assertJoin(`(block $out anyref (loop $top anyref
get_local $i
i32.const 1
i32.add
tee_local $i
i32.const 10
i32.eq
if
get_local $ref
return
end
br $top))
`);
assertJoin(`(block $out (loop $top
get_local $i
i32.const 1
i32.add
tee_local $i
i32.const 10
i32.le_s
if
br $top
else
get_local $ref
return
end
)) unreachable
`);
assertJoin(`(block $out anyref (loop $top
get_local $ref
get_local $i
i32.const 1
i32.add
tee_local $i
i32.const 10
i32.eq
br_if $out
br $top
) unreachable)
`);
assertJoin(`(block $out anyref (block $unreachable anyref (loop $top
get_local $ref
get_local $i
i32.const 1
i32.add
tee_local $i
br_table $unreachable $out
) unreachable))
`);
let x = { i: 42 }, y = { f: 53 };
exports = wasmEvalText(`(module
(func (export "test") (param $lhs anyref) (param $rhs anyref) (param $i i32) (result anyref)
get_local $lhs
get_local $rhs
get_local $i
select
)
)`).exports;
let result = exports.test(x, y, 0);
assertEq(result, y);
assertEq(result.i, undefined);
assertEq(result.f, 53);
assertEq(x.i, 42);
result = exports.test(x, y, 1);
assertEq(result, x);
assertEq(result.i, 42);
assertEq(result.f, undefined);
assertEq(y.f, 53);
// Anyref in params/result of imported functions.
let firstBaguette = new Baguette(13),
secondBaguette = new Baguette(37);
let imports = {
i: 0,
myBaguette: null,
funcs: {
param(x) {
if (this.i === 0) {
assertEq(x, firstBaguette);
assertEq(x.calories, 13);
assertEq(secondBaguette !== null, true);
} else if (this.i === 1 || this.i === 2) {
assertEq(x, secondBaguette);
assertEq(x.calories, 37);
assertEq(firstBaguette !== null, true);
} else if (this.i === 3) {
assertEq(x, null);
} else {
firstBaguette = null;
secondBaguette = null;
gc(); // evil mode
}
this.i++;
},
ret() {
return imports.myBaguette;
}
}
};
exports = wasmEvalText(`(module
(import $ret "funcs" "ret" (result anyref))
(import $param "funcs" "param" (param anyref))
(func (export "param") (param $x anyref) (param $y anyref)
get_local $y
get_local $x
call $param
call $param
)
(func (export "ret") (result anyref)
call $ret
)
)`, imports).exports;
exports.param(firstBaguette, secondBaguette);
exports.param(secondBaguette, null);
exports.param(firstBaguette, secondBaguette);
imports.myBaguette = null;
assertEq(exports.ret(), null);
imports.myBaguette = new Baguette(1337);
assertEq(exports.ret(), imports.myBaguette);
// Check lazy stubs generation.
exports = wasmEvalText(`(module
(import $mirror "funcs" "mirror" (param anyref) (result anyref))
(import $augment "funcs" "augment" (param anyref) (result anyref))
(global $count_f (mut i32) (i32.const 0))
(global $count_g (mut i32) (i32.const 0))
(func $f (param $param anyref) (result anyref)
i32.const 1
get_global $count_f
i32.add
set_global $count_f
get_local $param
call $augment
)
(func $g (param $param anyref) (result anyref)
i32.const 1
get_global $count_g
i32.add
set_global $count_g
get_local $param
call $mirror
)
(table (export "table") 10 anyfunc)
(elem (i32.const 0) $f $g $mirror $augment)
(type $table_type (func (param anyref) (result anyref)))
(func (export "call_indirect") (param $i i32) (param $ref anyref) (result anyref)
get_local $ref
get_local $i
call_indirect $table_type
)
(func (export "count_f") (result i32) get_global $count_f)
(func (export "count_g") (result i32) get_global $count_g)
)`, {
funcs: {
mirror(x) {
return x;
},
augment(x) {
x.i++;
x.newProp = "hello";
return x;
}
}
}).exports;
x = { i: 19 };
assertEq(exports.table.get(0)(x), x);
assertEq(x.i, 20);
assertEq(x.newProp, "hello");
assertEq(exports.count_f(), 1);
assertEq(exports.count_g(), 0);
x = { i: 21 };
assertEq(exports.table.get(1)(x), x);
assertEq(x.i, 21);
assertEq(typeof x.newProp, "undefined");
assertEq(exports.count_f(), 1);
assertEq(exports.count_g(), 1);
x = { i: 22 };
assertEq(exports.table.get(2)(x), x);
assertEq(x.i, 22);
assertEq(typeof x.newProp, "undefined");
assertEq(exports.count_f(), 1);
assertEq(exports.count_g(), 1);
x = { i: 23 };
assertEq(exports.table.get(3)(x), x);
assertEq(x.i, 24);
assertEq(x.newProp, "hello");
assertEq(exports.count_f(), 1);
assertEq(exports.count_g(), 1);

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

@ -0,0 +1 @@
|jit-test| test-also=--wasm-gc; include:wasm.js

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

@ -0,0 +1,28 @@
if (wasmGcEnabled()) {
quit();
}
const { CompileError, validate } = WebAssembly;
const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /(unrecognized opcode|bad type|invalid inline block type)/;
function assertValidateError(text) {
assertEq(validate(wasmTextToBinary(text)), false);
}
let simpleTests = [
"(module (func (drop (ref.null anyref))))",
"(module (func $test (local anyref)))",
"(module (func $test (param anyref)))",
"(module (func $test (result anyref) (ref.null anyref)))",
"(module (func $test (block anyref (unreachable)) unreachable))",
"(module (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
`(module (import "a" "b" (param anyref)))`,
`(module (import "a" "b" (result anyref)))`,
];
for (let src of simpleTests) {
print(src)
assertErrorMessage(() => wasmEvalText(src), CompileError, UNRECOGNIZED_OPCODE_OR_BAD_TYPE);
assertValidateError(src);
}

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

@ -296,6 +296,15 @@ struct RegI64 : public Register64
static RegI64 Invalid() { return RegI64(Register64::Invalid()); }
};
struct RegPtr : public Register
{
RegPtr() : Register(Register::Invalid()) {}
explicit RegPtr(Register reg) : Register(reg) {}
bool isValid() const { return *this != Invalid(); }
bool isInvalid() const { return !isValid(); }
static RegPtr Invalid() { return RegPtr(Register::Invalid()); }
};
struct RegF32 : public FloatRegister
{
RegF32() : FloatRegister() {}
@ -316,10 +325,21 @@ struct RegF64 : public FloatRegister
struct AnyReg
{
union {
RegI32 i32_;
RegI64 i64_;
RegPtr ref_;
RegF32 f32_;
RegF64 f64_;
};
enum { I32, I64, REF, F32, F64 } tag;
explicit AnyReg(RegI32 r) { tag = I32; i32_ = r; }
explicit AnyReg(RegI64 r) { tag = I64; i64_ = r; }
explicit AnyReg(RegF32 r) { tag = F32; f32_ = r; }
explicit AnyReg(RegF64 r) { tag = F64; f64_ = r; }
explicit AnyReg(RegPtr r) { tag = REF; ref_ = r; }
RegI32 i32() const {
MOZ_ASSERT(tag == I32);
@ -337,6 +357,11 @@ struct AnyReg
MOZ_ASSERT(tag == F64);
return f64_;
}
RegPtr ref() const {
MOZ_ASSERT(tag == REF);
return ref_;
}
AnyRegister any() const {
switch (tag) {
case F32: return AnyRegister(f32_);
@ -352,20 +377,14 @@ struct AnyReg
// only on 64-bit platforms.
MOZ_CRASH("AnyReg::any() on 32-bit platform");
#endif
case REF:
MOZ_CRASH("AnyReg::any() not implemented for ref types");
default:
MOZ_CRASH();
}
// Work around GCC 5 analysis/warning bug.
MOZ_CRASH("AnyReg::any(): impossible case");
}
union {
RegI32 i32_;
RegI64 i64_;
RegF32 f32_;
RegF64 f64_;
};
enum { I32, I64, F32, F64 } tag;
};
// Platform-specific registers.
@ -691,6 +710,10 @@ class BaseRegAlloc
#endif
}
bool isAvailablePtr(RegPtr r) {
return isAvailableGPR(r);
}
bool isAvailableF32(RegF32 r) {
return isAvailableFPU(r);
}
@ -726,6 +749,18 @@ class BaseRegAlloc
allocInt64(specific);
}
MOZ_MUST_USE RegPtr needPtr() {
if (!hasGPR())
bc.sync();
return RegPtr(allocGPR());
}
void needPtr(RegPtr specific) {
if (!isAvailablePtr(specific))
bc.sync();
allocGPR(specific);
}
MOZ_MUST_USE RegF32 needF32() {
if (!hasFPU<MIRType::Float32>())
bc.sync();
@ -758,6 +793,10 @@ class BaseRegAlloc
freeInt64(r);
}
void freePtr(RegPtr r) {
freeGPR(r);
}
void freeF64(RegF64 r) {
freeFPU(r);
}
@ -817,6 +856,10 @@ class BaseRegAlloc
void addKnownF64(RegF64 r) {
knownFPU_.add(r);
}
void addKnownRef(RegPtr r) {
knownGPR_.add(r);
}
};
#endif
};
@ -888,23 +931,28 @@ class ScratchF32 : public ScratchFloat32Scope
#endif
#ifdef RABALDR_SCRATCH_I32
class ScratchI32 : public BaseScratchRegister
template<class RegType>
class ScratchGPR : public BaseScratchRegister
{
public:
explicit ScratchI32(BaseRegAlloc& ra)
explicit ScratchGPR(BaseRegAlloc& ra)
: BaseScratchRegister(ra, BaseRegAlloc::ScratchKind::I32)
{}
operator RegI32() const { return RegI32(RabaldrScratchI32); }
operator RegType() const { return RegType(RabaldrScratchI32); }
};
#else
class ScratchI32 : public ScratchRegisterScope
template<class RegType>
class ScratchGPR : public ScratchRegisterScope
{
public:
explicit ScratchI32(MacroAssembler& m) : ScratchRegisterScope(m) {}
operator RegI32() const { return RegI32(Register(*this)); }
explicit ScratchGPR(MacroAssembler& m) : ScratchRegisterScope(m) {}
operator RegType() const { return RegType(Register(*this)); }
};
#endif
using ScratchI32 = ScratchGPR<RegI32>;
using ScratchPtr = ScratchGPR<RegPtr>;
#if defined(JS_CODEGEN_X86)
// ScratchEBX is a mnemonic device: For some atomic ops we really need EBX,
// no other register will do. And we would normally have to allocate that
@ -985,6 +1033,7 @@ BaseLocalIter::settle()
case MIRType::Int64:
case MIRType::Double:
case MIRType::Float32:
case MIRType::Pointer:
if (argsIter_->argInRegister())
frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
else
@ -1003,6 +1052,7 @@ BaseLocalIter::settle()
case ValType::I64:
case ValType::F32:
case ValType::F64:
case ValType::AnyRef:
mirType_ = ToMIRType(locals_[index_]);
frameOffset_ = pushLocal(MIRTypeToSize(mirType_));
break;
@ -1242,7 +1292,7 @@ class BaseStackFrame
masm.load64(Address(sp_, localOffset(src)), dest);
}
void loadLocalPtr(const Local& src, Register dest) {
void loadLocalPtr(const Local& src, RegPtr dest) {
masm.loadPtr(Address(sp_, localOffset(src)), dest);
}
@ -1551,7 +1601,7 @@ class BaseStackFrame
// Disambiguation: this loads a "Ptr" value from the stack, it does not load
// the "StackPtr".
void loadStackPtr(int32_t offset, Register dest) {
void loadStackPtr(int32_t offset, RegPtr dest) {
masm.loadPtr(Address(sp_, stackOffset(offset)), dest);
}
@ -1862,6 +1912,7 @@ class BaseCompiler final : public BaseCompilerInterface
RegI32 joinRegI32_;
RegI64 joinRegI64_;
RegPtr joinRegPtr_;
RegF32 joinRegF32_;
RegF64 joinRegF64_;
@ -1924,16 +1975,19 @@ class BaseCompiler final : public BaseCompilerInterface
bool isAvailableI32(RegI32 r) { return ra.isAvailableI32(r); }
bool isAvailableI64(RegI64 r) { return ra.isAvailableI64(r); }
bool isAvailableRef(RegPtr r) { return ra.isAvailablePtr(r); }
bool isAvailableF32(RegF32 r) { return ra.isAvailableF32(r); }
bool isAvailableF64(RegF64 r) { return ra.isAvailableF64(r); }
MOZ_MUST_USE RegI32 needI32() { return ra.needI32(); }
MOZ_MUST_USE RegI64 needI64() { return ra.needI64(); }
MOZ_MUST_USE RegPtr needRef() { return ra.needPtr(); }
MOZ_MUST_USE RegF32 needF32() { return ra.needF32(); }
MOZ_MUST_USE RegF64 needF64() { return ra.needF64(); }
void needI32(RegI32 specific) { ra.needI32(specific); }
void needI64(RegI64 specific) { ra.needI64(specific); }
void needRef(RegPtr specific) { ra.needPtr(specific); }
void needF32(RegF32 specific) { ra.needF32(specific); }
void needF64(RegF64 specific) { ra.needF64(specific); }
@ -1943,6 +1997,7 @@ class BaseCompiler final : public BaseCompilerInterface
void freeI32(RegI32 r) { ra.freeI32(r); }
void freeI64(RegI64 r) { ra.freeI64(r); }
void freeRef(RegPtr r) { ra.freePtr(r); }
void freeF32(RegF32 r) { ra.freeF32(r); }
void freeF64(RegF64 r) { ra.freeF64(r); }
@ -2018,6 +2073,10 @@ class BaseCompiler final : public BaseCompilerInterface
#endif
}
RegI32 narrowPtr(RegPtr r) {
return RegI32(r);
}
RegI32 lowPart(RegI64 r) {
#ifdef JS_PUNBOX64
return RegI32(r.reg);
@ -2050,6 +2109,11 @@ class BaseCompiler final : public BaseCompilerInterface
masm.move64(src, dest);
}
void moveRef(RegPtr src, RegPtr dest) {
if (src != dest)
masm.movePtr(src, dest);
}
void moveF64(RegF64 src, RegF64 dest) {
if (src != dest)
masm.moveDouble(src, dest);
@ -2065,6 +2129,8 @@ class BaseCompiler final : public BaseCompilerInterface
needI32(joinRegI32_);
else if (type == ExprType::I64)
needI64(joinRegI64_);
else if (type == ExprType::AnyRef)
needRef(joinRegPtr_);
}
void maybeUnreserveJoinRegI(ExprType type) {
@ -2072,6 +2138,8 @@ class BaseCompiler final : public BaseCompilerInterface
freeI32(joinRegI32_);
else if (type == ExprType::I64)
freeI64(joinRegI64_);
else if (type == ExprType::AnyRef)
freeRef(joinRegPtr_);
}
void maybeReserveJoinReg(ExprType type) {
@ -2088,6 +2156,9 @@ class BaseCompiler final : public BaseCompilerInterface
case ExprType::F64:
needF64(joinRegF64_);
break;
case ExprType::AnyRef:
needRef(joinRegPtr_);
break;
default:
break;
}
@ -2107,6 +2178,9 @@ class BaseCompiler final : public BaseCompilerInterface
case ExprType::F64:
freeF64(joinRegF64_);
break;
case ExprType::AnyRef:
freeRef(joinRegPtr_);
break;
default:
break;
}
@ -2127,6 +2201,10 @@ class BaseCompiler final : public BaseCompilerInterface
struct Stk
{
private:
Stk() : kind_(Unknown), i64val_(0) {}
public:
enum Kind
{
// The Mem opcodes are all clustered at the beginning to
@ -2135,6 +2213,7 @@ class BaseCompiler final : public BaseCompilerInterface
MemI64, // 64-bit integer stack value ("offs")
MemF32, // 32-bit floating stack value ("offs")
MemF64, // 64-bit floating stack value ("offs")
MemRef, // reftype (pointer wide) stack value ("offs")
// The Local opcodes follow the Mem opcodes for a similar
// quick test within hasLocal().
@ -2142,47 +2221,61 @@ class BaseCompiler final : public BaseCompilerInterface
LocalI64, // Local int64 var ("slot")
LocalF32, // Local float32 var ("slot")
LocalF64, // Local double var ("slot")
LocalRef, // Local reftype (pointer wide) var ("slot")
RegisterI32, // 32-bit integer register ("i32reg")
RegisterI64, // 64-bit integer register ("i64reg")
RegisterF32, // 32-bit floating register ("f32reg")
RegisterF64, // 64-bit floating register ("f64reg")
RegisterRef, // reftype (pointer wide) register ("refReg")
ConstI32, // 32-bit integer constant ("i32val")
ConstI64, // 64-bit integer constant ("i64val")
ConstF32, // 32-bit floating constant ("f32val")
ConstF64, // 64-bit floating constant ("f64val")
ConstRef, // reftype (pointer wide) constant ("refval")
Unknown,
};
Kind kind_;
static const Kind MemLast = MemF64;
static const Kind LocalLast = LocalF64;
static const Kind MemLast = MemRef;
static const Kind LocalLast = LocalRef;
union {
RegI32 i32reg_;
RegI64 i64reg_;
RegPtr refReg_;
RegF32 f32reg_;
RegF64 f64reg_;
int32_t i32val_;
int64_t i64val_;
intptr_t refval_;
float f32val_;
double f64val_;
uint32_t slot_;
uint32_t offs_;
};
explicit Stk(RegI32 r) : kind_(RegisterI32), i32reg_(r) {}
explicit Stk(RegI64 r) : kind_(RegisterI64), i64reg_(r) {}
explicit Stk(RegF32 r) : kind_(RegisterF32), f32reg_(r) {}
explicit Stk(RegF64 r) : kind_(RegisterF64), f64reg_(r) {}
explicit Stk(int32_t v) : kind_(ConstI32), i32val_(v) {}
explicit Stk(int64_t v) : kind_(ConstI64), i64val_(v) {}
explicit Stk(float v) : kind_(ConstF32), f32val_(v) {}
explicit Stk(double v) : kind_(ConstF64), f64val_(v) {}
explicit Stk(RegI32 r) : kind_(RegisterI32), i32reg_(r) {}
explicit Stk(RegI64 r) : kind_(RegisterI64), i64reg_(r) {}
explicit Stk(RegPtr r) : kind_(RegisterRef), refReg_(r) {}
explicit Stk(RegF32 r) : kind_(RegisterF32), f32reg_(r) {}
explicit Stk(RegF64 r) : kind_(RegisterF64), f64reg_(r) {}
explicit Stk(int32_t v) : kind_(ConstI32), i32val_(v) {}
explicit Stk(int64_t v) : kind_(ConstI64), i64val_(v) {}
explicit Stk(float v) : kind_(ConstF32), f32val_(v) {}
explicit Stk(double v) : kind_(ConstF64), f64val_(v) {}
explicit Stk(Kind k, uint32_t v) : kind_(k), slot_(v) {
MOZ_ASSERT(k > MemLast && k <= LocalLast);
}
static Stk StkRef(intptr_t v) {
Stk s;
s.kind_ = ConstRef;
s.refval_ = v;
return s;
}
void setOffs(Kind k, uint32_t v) { MOZ_ASSERT(k <= MemLast); kind_ = k; offs_ = v; }
@ -2191,11 +2284,13 @@ class BaseCompiler final : public BaseCompilerInterface
RegI32 i32reg() const { MOZ_ASSERT(kind_ == RegisterI32); return i32reg_; }
RegI64 i64reg() const { MOZ_ASSERT(kind_ == RegisterI64); return i64reg_; }
RegPtr refReg() const { MOZ_ASSERT(kind_ == RegisterRef); return refReg_; }
RegF32 f32reg() const { MOZ_ASSERT(kind_ == RegisterF32); return f32reg_; }
RegF64 f64reg() const { MOZ_ASSERT(kind_ == RegisterF64); return f64reg_; }
int32_t i32val() const { MOZ_ASSERT(kind_ == ConstI32); return i32val_; }
int64_t i64val() const { MOZ_ASSERT(kind_ == ConstI64); return i64val_; }
intptr_t refval() const { MOZ_ASSERT(kind_ == ConstRef); return refval_; }
// For these two, use an out-param instead of simply returning, to
// use the normal stack and not the x87 FP stack (which has effect on
@ -2215,6 +2310,10 @@ class BaseCompiler final : public BaseCompilerInterface
stk_.infallibleEmplaceBack(Stk(Forward<Args>(args)...));
}
void pushConstRef(intptr_t v) {
stk_.infallibleEmplaceBack(Stk::StkRef(v));
}
void loadConstI32(const Stk& src, RegI32 dest) {
moveImm32(src.i32val(), dest);
}
@ -2247,6 +2346,22 @@ class BaseCompiler final : public BaseCompilerInterface
moveI64(src.i64reg(), dest);
}
void loadConstRef(const Stk& src, RegPtr dest) {
moveImmRef(src.refval(), dest);
}
void loadMemRef(const Stk& src, RegPtr dest) {
fr.loadStackPtr(src.offs(), dest);
}
void loadLocalRef(const Stk& src, RegPtr dest) {
fr.loadLocalPtr(localFromSlot(src.slot(), MIRType::Pointer), dest);
}
void loadRegisterRef(const Stk& src, RegPtr dest) {
moveRef(src.refReg(), dest);
}
void loadConstF64(const Stk& src, RegF64 dest) {
double d;
src.f64val(&d);
@ -2399,6 +2514,25 @@ class BaseCompiler final : public BaseCompilerInterface
}
}
void loadRef(const Stk& src, RegPtr dest) {
switch (src.kind()) {
case Stk::ConstRef:
loadConstRef(src, dest);
break;
case Stk::MemRef:
loadMemRef(src, dest);
break;
case Stk::LocalRef:
loadLocalRef(src, dest);
break;
case Stk::RegisterRef:
loadRegisterRef(src, dest);
break;
default:
MOZ_CRASH("Compiler bug: expected ref on stack");
}
}
// Flush all local and register value stack elements to memory.
//
// TODO / OPTIMIZE: As this is fairly expensive and causes worse
@ -2501,6 +2635,19 @@ class BaseCompiler final : public BaseCompilerInterface
v.setOffs(Stk::MemF32, offs);
break;
}
case Stk::LocalRef: {
ScratchPtr scratch(*this);
loadLocalRef(v, scratch);
uint32_t offs = fr.pushPtr(scratch);
v.setOffs(Stk::MemRef, offs);
break;
}
case Stk::RegisterRef: {
uint32_t offs = fr.pushPtr(v.refReg());
freeRef(v.refReg());
v.setOffs(Stk::MemRef, offs);
break;
}
default: {
break;
}
@ -2544,6 +2691,11 @@ class BaseCompiler final : public BaseCompilerInterface
push(r);
}
void pushRef(RegPtr r) {
MOZ_ASSERT(!isAvailableRef(r));
push(r);
}
void pushF64(RegF64 r) {
MOZ_ASSERT(!isAvailableF64(r));
push(r);
@ -2564,6 +2716,10 @@ class BaseCompiler final : public BaseCompilerInterface
push(v);
}
void pushRef(intptr_t v) {
pushConstRef(v);
}
void pushF64(double v) {
push(v);
}
@ -2584,6 +2740,10 @@ class BaseCompiler final : public BaseCompilerInterface
push(Stk::LocalI64, slot);
}
void pushLocalRef(uint32_t slot) {
push(Stk::LocalRef, slot);
}
void pushLocalF64(uint32_t slot) {
push(Stk::LocalF64, slot);
}
@ -2698,6 +2858,54 @@ class BaseCompiler final : public BaseCompilerInterface
return specific;
}
// Call only from other popRef() variants.
// v must be the stack top. May pop the CPU stack.
void popRef(const Stk& v, RegPtr dest) {
MOZ_ASSERT(&v == &stk_.back());
switch (v.kind()) {
case Stk::ConstRef:
loadConstRef(v, dest);
break;
case Stk::LocalRef:
loadLocalRef(v, dest);
break;
case Stk::MemRef:
fr.popPtr(dest);
break;
case Stk::RegisterRef:
loadRegisterRef(v, dest);
break;
default:
MOZ_CRASH("Compiler bug: expected ref on stack");
}
}
RegPtr popRef(RegPtr specific) {
Stk& v = stk_.back();
if (!(v.kind() == Stk::RegisterRef && v.refReg() == specific)) {
needRef(specific);
popRef(v, specific);
if (v.kind() == Stk::RegisterRef)
freeRef(v.refReg());
}
stk_.popBack();
return specific;
}
MOZ_MUST_USE RegPtr popRef() {
Stk& v = stk_.back();
RegPtr r;
if (v.kind() == Stk::RegisterRef)
r = v.refReg();
else
popRef(v, (r = needRef()));
stk_.popBack();
return r;
}
// Call only from other popF64() variants.
// v must be the stack top. May pop the CPU stack.
@ -2914,6 +3122,12 @@ class BaseCompiler final : public BaseCompilerInterface
k == Stk::LocalF32);
return Some(AnyReg(popF32(joinRegF32_)));
}
case ExprType::AnyRef: {
DebugOnly<Stk::Kind> k(stk_.back().kind());
MOZ_ASSERT(k == Stk::RegisterRef || k == Stk::ConstRef || k == Stk::MemRef ||
k == Stk::LocalRef);
return Some(AnyReg(popRef(joinRegPtr_)));
}
default: {
MOZ_CRASH("Compiler bug: unexpected expression type");
}
@ -2944,6 +3158,10 @@ class BaseCompiler final : public BaseCompilerInterface
MOZ_ASSERT(isAvailableF64(joinRegF64_));
needF64(joinRegF64_);
return Some(AnyReg(joinRegF64_));
case ExprType::AnyRef:
MOZ_ASSERT(isAvailableRef(joinRegPtr_));
needRef(joinRegPtr_);
return Some(AnyReg(joinRegPtr_));
case ExprType::Void:
return Nothing();
default:
@ -2967,6 +3185,9 @@ class BaseCompiler final : public BaseCompilerInterface
case AnyReg::F32:
pushF32(r->f32());
break;
case AnyReg::REF:
pushRef(r->ref());
break;
}
}
@ -2986,6 +3207,9 @@ class BaseCompiler final : public BaseCompilerInterface
case AnyReg::F32:
freeF32(r->f32());
break;
case AnyReg::REF:
freeRef(r->ref());
break;
}
}
@ -2998,6 +3222,7 @@ class BaseCompiler final : public BaseCompilerInterface
for (uint32_t i = stk_.length() - 1; numval > 0; numval--, i--) {
Stk& v = stk_[i];
switch (v.kind()) {
case Stk::MemRef: size += BaseStackFrame::StackSizeOfPtr; break;
case Stk::MemI32: size += BaseStackFrame::StackSizeOfPtr; break;
case Stk::MemI64: size += BaseStackFrame::StackSizeOfInt64; break;
case Stk::MemF64: size += BaseStackFrame::StackSizeOfDouble; break;
@ -3024,6 +3249,9 @@ class BaseCompiler final : public BaseCompilerInterface
case Stk::RegisterF32:
freeF32(v.f32reg());
break;
case Stk::RegisterRef:
freeRef(v.refReg());
break;
default:
break;
}
@ -3070,6 +3298,9 @@ class BaseCompiler final : public BaseCompilerInterface
case Stk::RegisterF64:
check.addKnownF64(item.f64reg());
break;
case Stk::RegisterRef:
check.addKnownRef(item.refReg());
break;
default:
break;
}
@ -3159,6 +3390,9 @@ class BaseCompiler final : public BaseCompilerInterface
case MIRType::Int64:
fr.storeLocalI64(RegI64(i->gpr64()), l);
break;
case MIRType::Pointer:
fr.storeLocalPtr(RegPtr(i->gpr()), l);
break;
case MIRType::Double:
fr.storeLocalF64(RegF64(i->fpu()), l);
break;
@ -3488,6 +3722,17 @@ class BaseCompiler final : public BaseCompilerInterface
}
break;
}
case ValType::AnyRef: {
ABIArg argLoc = call->abi.next(MIRType::Pointer);
if (argLoc.kind() == ABIArg::Stack) {
ScratchPtr scratch(*this);
loadRef(arg, scratch);
masm.storePtr(scratch, Address(masm.getStackPointer(), argLoc.offsetFromArgBase()));
} else {
loadRef(arg, RegPtr(argLoc.gpr()));
}
break;
}
default:
MOZ_CRASH("Function argument type");
}
@ -3561,6 +3806,10 @@ class BaseCompiler final : public BaseCompilerInterface
masm.move64(Imm64(v), dest);
}
void moveImmRef(intptr_t v, RegPtr dest) {
masm.movePtr(ImmWord(v), dest);
}
void moveImmF32(float f, RegF32 dest) {
masm.loadConstantFloat32(f, dest);
}
@ -3691,6 +3940,13 @@ class BaseCompiler final : public BaseCompilerInterface
return r;
}
RegPtr captureReturnedRef() {
RegPtr r = RegPtr(ReturnReg);
MOZ_ASSERT(isAvailableRef(r));
needRef(r);
return r;
}
void returnCleanup(bool popStack) {
if (popStack)
fr.popStackBeforeBranch(controlOutermost().stackHeight);
@ -5275,6 +5531,11 @@ class BaseCompiler final : public BaseCompilerInterface
*r0 = popF64();
}
void pop2xRef(RegPtr* r0, RegPtr* r1) {
*r1 = popRef();
*r0 = popRef();
}
RegI32 popI64ToI32() {
RegI64 r = popI64();
return narrowI64(r);
@ -5614,6 +5875,9 @@ class BaseCompiler final : public BaseCompilerInterface
MOZ_MUST_USE bool emitGrowMemory();
MOZ_MUST_USE bool emitCurrentMemory();
MOZ_MUST_USE bool emitRefNull();
void emitRefIsNull();
MOZ_MUST_USE bool emitAtomicCmpXchg(ValType type, Scalar::Type viewType);
MOZ_MUST_USE bool emitAtomicLoad(ValType type, Scalar::Type viewType);
MOZ_MUST_USE bool emitAtomicRMW(ValType type, Scalar::Type viewType, AtomicOp op);
@ -6851,10 +7115,9 @@ BaseCompiler::sniffConditionalControlCmp(Cond compareOp, ValType operandType)
OpBytes op;
iter_.peekOp(&op);
switch (op.b0) {
case uint16_t(Op::Select):
MOZ_FALLTHROUGH;
case uint16_t(Op::BrIf):
case uint16_t(Op::If):
case uint16_t(Op::Select):
setLatentCompare(compareOp, operandType);
return true;
default:
@ -7436,6 +7699,12 @@ BaseCompiler::doReturn(ExprType type, bool popStack)
freeF32(rv);
break;
}
case ExprType::AnyRef: {
RegPtr rv = popRef(RegPtr(ReturnReg));
returnCleanup(popStack);
freeRef(rv);
break;
}
default: {
MOZ_CRASH("Function return type");
}
@ -7500,6 +7769,11 @@ BaseCompiler::pushReturned(const FunctionCall& call, ExprType type)
pushF64(rv);
break;
}
case ExprType::AnyRef: {
RegPtr rv = captureReturnedRef();
pushRef(rv);
break;
}
default:
MOZ_CRASH("Function return type");
}
@ -7827,6 +8101,9 @@ BaseCompiler::emitGetLocal()
case ValType::F32:
pushLocalF32(slot);
break;
case ValType::AnyRef:
pushLocalRef(slot);
break;
default:
MOZ_CRASH("Local variable type");
}
@ -7883,6 +8160,16 @@ BaseCompiler::emitSetOrTeeLocal(uint32_t slot)
pushF32(rv);
break;
}
case ValType::AnyRef: {
RegPtr rv = popRef();
syncLocal(slot);
fr.storeLocalPtr(rv, localFromSlot(slot, MIRType::Pointer));
if (isSetLocal)
freeRef(rv);
else
pushRef(rv);
break;
}
default:
MOZ_CRASH("Local variable type");
}
@ -8403,6 +8690,16 @@ BaseCompiler::emitSelect()
pushF64(r);
break;
}
case ValType::AnyRef: {
RegPtr r, rs;
pop2xRef(&r, &rs);
emitBranchPerform(&b);
moveRef(rs, r);
masm.bind(&done);
freeRef(rs);
pushRef(r);
break;
}
default: {
MOZ_CRASH("select type");
}
@ -8557,6 +8854,29 @@ BaseCompiler::emitCurrentMemory()
return true;
}
bool
BaseCompiler::emitRefNull()
{
if (!iter_.readRefNull())
return false;
if (deadCode_)
return true;
pushRef(NULLREF_VALUE);
return true;
}
void
BaseCompiler::emitRefIsNull()
{
RegPtr r = popRef();
RegI32 rd = narrowPtr(r);
masm.cmpPtrSet(Assembler::Equal, r, ImmWord(NULLREF_VALUE), rd);
pushI32(rd);
}
bool
BaseCompiler::emitAtomicCmpXchg(ValType type, Scalar::Type viewType)
{
@ -9424,6 +9744,19 @@ BaseCompiler::emitBody()
case uint16_t(Op::CurrentMemory):
CHECK_NEXT(emitCurrentMemory());
#ifdef ENABLE_WASM_GC
case uint16_t(Op::RefNull):
if (env_.gcTypesEnabled == HasGcTypes::False)
return iter_.unrecognizedOpcode(&op);
CHECK_NEXT(emitRefNull());
break;
case uint16_t(Op::RefIsNull):
if (env_.gcTypesEnabled == HasGcTypes::False)
return iter_.unrecognizedOpcode(&op);
CHECK_NEXT(emitConversion(emitRefIsNull, ValType::AnyRef, ValType::I32));
break;
#endif
// Numeric operations
case uint16_t(Op::NumericPrefix): {
#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
@ -9702,6 +10035,7 @@ BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
fr(*masm),
joinRegI32_(RegI32(ReturnReg)),
joinRegI64_(RegI64(ReturnReg64)),
joinRegPtr_(RegPtr(ReturnReg)),
joinRegF32_(RegF32(ReturnFloat32Reg)),
joinRegF64_(RegF64(ReturnDoubleReg))
{

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

@ -98,6 +98,10 @@ enum class ValType
typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
// The representation of a null reference value throughout the compiler.
static const intptr_t NULLREF_VALUE = intptr_t((void*)nullptr);
enum class DefinitionKind
{
Function = 0x00,