Skip to content
2 changes: 1 addition & 1 deletion cli/asc.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@
"description": [
"Enables WebAssembly features being disabled by default.",
"",
" nontrapping-f2i Non-trapping float to integer ops.",
" bulk-memory Bulk memory operations.",
" simd SIMD types and operations.",
" threads Threading and atomic operations.",
Expand All @@ -239,6 +238,7 @@
"",
" mutable-globals Mutable global imports and exports.",
" sign-extension Sign-extension operations",
" nontrapping-f2i Non-trapping float to integer ops.",
""
],
"type": "S",
Expand Down
94 changes: 64 additions & 30 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export class Options {
/** Global aliases, mapping alias names as the key to internal names to be aliased as the value. */
globalAliases: Map<string,string> | null = null;
/** Features to activate by default. These are the finished proposals. */
features: Feature = Feature.MUTABLE_GLOBALS | Feature.SIGN_EXTENSION;
features: Feature = Feature.MUTABLE_GLOBALS | Feature.SIGN_EXTENSION | Feature.NONTRAPPING_F2I;
/** If true, disallows unsafe features in user code. */
noUnsafe: bool = false;
/** If true, enables pedantic diagnostics. */
Expand Down Expand Up @@ -428,7 +428,7 @@ export class Compiler extends DiagnosticEmitter {
}
var featureFlags: FeatureFlags = 0;
if (options.hasFeature(Feature.SIGN_EXTENSION)) featureFlags |= FeatureFlags.SignExt;
if (options.hasFeature(Feature.MUTABLE_GLOBALS)) featureFlags |= FeatureFlags.MutableGloabls;
if (options.hasFeature(Feature.MUTABLE_GLOBALS)) featureFlags |= FeatureFlags.MutableGlobals;
if (options.hasFeature(Feature.NONTRAPPING_F2I)) featureFlags |= FeatureFlags.TruncSat;
if (options.hasFeature(Feature.BULK_MEMORY)) featureFlags |= FeatureFlags.BulkMemory;
if (options.hasFeature(Feature.SIMD)) featureFlags |= FeatureFlags.SIMD;
Expand Down Expand Up @@ -10271,38 +10271,72 @@ export class Compiler extends DiagnosticEmitter {
: expr;
}
case TypeKind.F32: {
// 0 < abs(bitCast(x)) <= bitCast(Infinity) or
// (reinterpret<u32>(x) & 0x7FFFFFFF) - 1 <= 0x7F800000 - 1
//
// and finally:
// (reinterpret<u32>(x) << 1) - (1 << 1) <= ((0x7F800000 - 1) << 1)
return module.binary(BinaryOp.LeU32,
module.binary(BinaryOp.SubI32,
module.binary(BinaryOp.ShlI32,
module.unary(UnaryOp.ReinterpretF32ToI32, expr),
module.i32(1)
if (
this.options.shrinkLevelHint > 1 &&
this.options.hasFeature(Feature.NONTRAPPING_F2I)
) {
// Use more compact but slower 5-byte (3 bytes in best case) approach
// !!(i32.trunc_sat_f32_u(f32.ceil(f32.abs(x))))
return module.unary(UnaryOp.EqzI32,
module.unary(UnaryOp.EqzI32,
module.unary(UnaryOp.TruncSatF32ToU32,
module.unary(UnaryOp.CeilF32,
module.unary(UnaryOp.AbsF32, expr)
)
)
)
);
} else {
// 0 < abs(bitCast(x)) <= bitCast(Infinity) or
// (reinterpret<u32>(x) & 0x7FFFFFFF) - 1 <= 0x7F800000 - 1
//
// and finally:
// (reinterpret<u32>(x) << 1) - (1 << 1) <= ((0x7F800000 - 1) << 1)
return module.binary(BinaryOp.LeU32,
module.binary(BinaryOp.SubI32,
module.binary(BinaryOp.ShlI32,
module.unary(UnaryOp.ReinterpretF32ToI32, expr),
module.i32(1)
),
module.i32(2) // 1 << 1
),
module.i32(2) // 1 << 1
),
module.i32(0xFEFFFFFE) // (0x7F800000 - 1) << 1
);
module.i32(0xFEFFFFFE) // (0x7F800000 - 1) << 1
);
}
}
case TypeKind.F64: {
// 0 < abs(bitCast(x)) <= bitCast(Infinity) or
// (reinterpret<u64>(x) & 0x7FFFFFFFFFFFFFFF) - 1 <= 0x7FF0000000000000 - 1
//
// and finally:
// (reinterpret<u64>(x) << 1) - (1 << 1) <= ((0x7FF0000000000000 - 1) << 1)
return module.binary(BinaryOp.LeU64,
module.binary(BinaryOp.SubI64,
module.binary(BinaryOp.ShlI64,
module.unary(UnaryOp.ReinterpretF64ToI64, expr),
module.i64(1)
if (
this.options.shrinkLevelHint > 1 &&
this.options.hasFeature(Feature.NONTRAPPING_F2I)
) {
// Use more compact but slower 5-byte (3 bytes in best case) approach
// !!(i32.trunc_sat_f64_u(f64.ceil(f64.abs(x))))
return module.unary(UnaryOp.EqzI32,
module.unary(UnaryOp.EqzI32,
module.unary(UnaryOp.TruncSatF64ToU32,
module.unary(UnaryOp.CeilF64,
module.unary(UnaryOp.AbsF64, expr)
)
)
)
);
} else {
// 0 < abs(bitCast(x)) <= bitCast(Infinity) or
// (reinterpret<u64>(x) & 0x7FFFFFFFFFFFFFFF) - 1 <= 0x7FF0000000000000 - 1
//
// and finally:
// (reinterpret<u64>(x) << 1) - (1 << 1) <= ((0x7FF0000000000000 - 1) << 1)
return module.binary(BinaryOp.LeU64,
module.binary(BinaryOp.SubI64,
module.binary(BinaryOp.ShlI64,
module.unary(UnaryOp.ReinterpretF64ToI64, expr),
module.i64(1)
),
module.i64(2) // 1 << 1
),
module.i64(2) // 1 << 1
),
module.i64(0xFFFFFFFE, 0xFFDFFFFF) // (0x7FF0000000000000 - 1) << 1
);
module.i64(0xFFFFFFFE, 0xFFDFFFFF) // (0x7FF0000000000000 - 1) << 1
);
}
}
case TypeKind.V128: {
return module.unary(UnaryOp.AnyTrueV128, expr);
Expand Down
2 changes: 1 addition & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export namespace TypeRef {
export enum FeatureFlags {
MVP = 0 /* _BinaryenFeatureMVP */,
Atomics = 1 /* _BinaryenFeatureAtomics */,
MutableGloabls = 2 /* _BinaryenFeatureMutableGlobals */,
MutableGlobals = 2 /* _BinaryenFeatureMutableGlobals */,
TruncSat = 4 /* _BinaryenFeatureNontrappingFPToInt */,
SIMD = 8 /* _BinaryenFeatureSIMD128 */,
BulkMemory = 16 /* _BinaryenFeatureBulkMemory */,
Expand Down
66 changes: 47 additions & 19 deletions std/assembly/typedarray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1894,7 +1894,10 @@ function WRAP<TArray extends ArrayBufferView, T>(

// @ts-ignore: decorator
@inline
function SET<TArray extends ArrayBufferView, T, UArray extends ArrayBufferView, U>(
function SET<
TArray extends ArrayBufferView, T extends number,
UArray extends ArrayBufferView, U extends number
>(
target: TArray,
source: UArray,
offset: i32 = 0
Expand All @@ -1906,29 +1909,41 @@ function SET<TArray extends ArrayBufferView, T, UArray extends ArrayBufferView,

// Uncaught RangeError: offset is out of bounds
if (offset < 0) throw new RangeError(E_INDEXOUTOFRANGE);
if (source.length + offset > target.length) throw new RangeError(E_INDEXOUTOFRANGE);

let slen = source.length;
let tlen = target.length;

if (slen + offset > tlen) throw new RangeError(E_INDEXOUTOFRANGE);

let targetDataStart = target.dataStart + (<usize>offset << alignof<T>());
let sourceDataStart = source.dataStart;

// if the types align and match, use memory.copy() instead of manual loop
if (isInteger<T>() == isInteger<U>() && alignof<T>() == alignof<U>() &&
!(target instanceof Uint8ClampedArray && isSigned<U>())) {
memory.copy(
target.dataStart + (<usize>offset << alignof<T>()),
source.dataStart,
source.byteLength
);
if (
isInteger<T>() == isInteger<U>() &&
alignof<T>() == alignof<U>() &&
!(target instanceof Uint8ClampedArray && isSigned<U>())
) {
memory.copy(targetDataStart, sourceDataStart, source.byteLength);
} else {
let targetDataStart = target.dataStart + (<usize>offset << alignof<T>());
let sourceDataStart = source.dataStart;
let count = source.length;
for (let i = 0; i < count; i++) {
for (let i = 0; i < slen; i++) {
// if TArray is Uint8ClampedArray, then values must be clamped
if (target instanceof Uint8ClampedArray) {
if (isFloat<U>()) {
let value = load<U>(sourceDataStart + (<usize>i << alignof<U>()));
store<T>(
targetDataStart + (<usize>i << alignof<T>()),
isFinite<U>(value) ? <T>max<U>(0, min<U>(255, value)) : <T>0
);
if (!ASC_FEATURE_NONTRAPPING_F2I) {
store<T>(
targetDataStart + (<usize>i << alignof<T>()),
// @ts-ignore: cast
isFinite<U>(value) ? <T>max<U>(0, min<U>(255, value)) : <T>0
);
} else {
store<T>(
targetDataStart + (<usize>i << alignof<T>()),
// @ts-ignore: cast
<T>max<U>(0, min<U>(255, value))
);
}
} else {
let value = load<U>(sourceDataStart + (<usize>i << alignof<U>()));
if (!isSigned<U>()) {
Expand All @@ -1954,12 +1969,25 @@ function SET<TArray extends ArrayBufferView, T, UArray extends ArrayBufferView,
// if U is a float, then casting float to int must include a finite check
} else if (isFloat<U>() && !isFloat<T>()) {
let value = load<U>(sourceDataStart + (<usize>i << alignof<U>()));
// @ts-ignore: cast to T is valid for numeric types here
store<T>(targetDataStart + (<usize>i << alignof<T>()), isFinite<U>(value) ? <T>value : 0);

if (!ASC_FEATURE_NONTRAPPING_F2I) {
store<T>(
targetDataStart + (<usize>i << alignof<T>()),
// @ts-ignore: cast
isFinite<U>(value) ? <T>value : <T>0
);
} else {
store<T>(
targetDataStart + (<usize>i << alignof<T>()),
// @ts-ignore: cast
<T>value
);
}
} else if (isFloat<T>() && !isFloat<U>()) {
// @ts-ignore: In this case the <T> conversion is required
store<T>(targetDataStart + (<usize>i << alignof<T>()), <T>load<U>(sourceDataStart + (<usize>i << alignof<U>())));
} else {
// @ts-ignore: cast
store<T>(targetDataStart + (<usize>i << alignof<T>()), load<U>(sourceDataStart + (<usize>i << alignof<U>())));
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/compiler/asc-constants.untouched.wat
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
(global $~lib/ASC_SHRINK_LEVEL i32 (i32.const 0))
(global $~lib/ASC_FEATURE_SIGN_EXTENSION i32 (i32.const 1))
(global $~lib/ASC_FEATURE_MUTABLE_GLOBALS i32 (i32.const 1))
(global $~lib/ASC_FEATURE_NONTRAPPING_F2I i32 (i32.const 0))
(global $~lib/ASC_FEATURE_NONTRAPPING_F2I i32 (i32.const 1))
(global $~lib/ASC_FEATURE_BULK_MEMORY i32 (i32.const 0))
(global $~lib/ASC_FEATURE_SIMD i32 (i32.const 0))
(global $~lib/ASC_FEATURE_THREADS i32 (i32.const 0))
Expand Down Expand Up @@ -43,7 +43,7 @@
drop
i32.const 1
drop
i32.const 0
i32.const 1
drop
i32.const 0
drop
Expand Down
2 changes: 1 addition & 1 deletion tests/compiler/binary.untouched.wat
Original file line number Diff line number Diff line change
Expand Up @@ -2654,7 +2654,7 @@
f64.convert_i64_s
f64.const 1
call $~lib/math/NativeMath.pow
i64.trunc_f64_s
i64.trunc_sat_f64_s
global.set $binary/I
global.get $binary/I
i64.const 1
Expand Down
5 changes: 5 additions & 0 deletions tests/compiler/bool-Oz.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"asc_flags": [
"-Oz"
]
}
6 changes: 6 additions & 0 deletions tests/compiler/bool-Oz.optimized.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(memory $0 1)
(data (i32.const 12) ",")
(data (i32.const 24) "\01\00\00\00\14\00\00\00b\00o\00o\00l\00-\00O\00z\00.\00t\00s")
(export "memory" (memory $0))
)
61 changes: 61 additions & 0 deletions tests/compiler/bool-Oz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
var f = <f32>2;
assert(<bool>f == true);
var f0 = <f32>+0.0;
assert(<bool>f0 == false);
var f1 = <f32>-0.0;
assert(<bool>f1 == false);
var f2 = <f32>+NaN;
assert(<bool>f2 == false);
var f3 = <f32>-NaN;
assert(<bool>f3 == false);
var f4 = +f32.MAX_VALUE;
assert(<bool>f4 == true);
var f5 = -f32.MAX_VALUE;
assert(<bool>f5 == true);
var f6 = <f32>+Infinity;
assert(<bool>f6 == true);
var f7 = <f32>-Infinity;
assert(<bool>f7 == true);
var f8 = +f32.MIN_VALUE;
assert(<bool>f8 == true);
var f9 = -f32.MIN_VALUE;
assert(<bool>f9 == true);
var f10 = reinterpret<f32>(1);
assert(<bool>f10 == true);
var f11 = reinterpret<f32>(0x7F800000 - 1);
assert(<bool>f11 == true);
var f12 = reinterpret<f32>(0x7F800000 + 1);
assert(<bool>f12 == false);
var f13 = reinterpret<f32>(0xFF800000 + 1);
assert(<bool>f13 == false);

var F = <f64>2;
assert(<bool>F == true);
var F0 = <f64>+0.0;
assert(<bool>F0 == false);
var F1 = <f64>-0.0;
assert(<bool>F1 == false);
var F2 = <f64>+NaN;
assert(<bool>F2 == false);
var F3 = <f64>-NaN;
assert(<bool>F3 == false);
var F4 = +f64.MAX_VALUE;
assert(<bool>F4 == true);
var F5 = -f64.MAX_VALUE;
assert(<bool>F5 == true);
var F6 = +Infinity;
assert(<bool>F6 == true);
var F7 = -Infinity;
assert(<bool>F7 == true);
var F8 = +f64.MIN_VALUE;
assert(<bool>F8 == true);
var F9 = -f64.MIN_VALUE;
assert(<bool>F9 == true);
var F10 = reinterpret<f64>(1);
assert(<bool>F10 == true);
var F11 = reinterpret<f64>(0x7FF0000000000000 - 1);
assert(<bool>F11 == true);
var F12 = reinterpret<f64>(0x7FF0000000000000 + 1);
assert(<bool>F12 == false);
var F13 = reinterpret<f64>(0xFFF0000000000000 + 1);
assert(<bool>F13 == false);
6 changes: 6 additions & 0 deletions tests/compiler/bool-Oz.untouched.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(memory $0 1)
(data (i32.const 12) ",")
(data (i32.const 24) "\01\00\00\00\14\00\00\00b\00o\00o\00l\00-\00O\00z\00.\00t\00s")
(export "memory" (memory $0))
)
2 changes: 1 addition & 1 deletion tests/compiler/number.untouched.wat
Original file line number Diff line number Diff line change
Expand Up @@ -5186,7 +5186,7 @@
f64.add
local.set $16
local.get $16
i32.trunc_f64_s
i32.trunc_sat_f64_s
local.set $15
local.get $15
local.get $15
Expand Down
Loading