Skip to content

Add a raw static memory mechanism #1233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified lib/loader/tests/build/default.wasm
Binary file not shown.
Binary file modified lib/loader/tests/build/legacy.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion lib/loader/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function test(file) {

// should export memory
assert(exports.memory instanceof WebAssembly.Memory);
assert(typeof exports.memory.copy === "function");
assert(typeof exports.memory.compare === "function");

// should be able to get an exported string
assert.strictEqual(exports.__getString(exports.COLOR), "red");
Expand Down
296 changes: 189 additions & 107 deletions src/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ import {
Expression,
LiteralKind,
StringLiteralExpression,
CallExpression
CallExpression,
NodeKind,
LiteralExpression,
ArrayLiteralExpression
} from "./ast";

import {
Expand Down Expand Up @@ -581,6 +584,7 @@ export namespace BuiltinNames {
export const memory_grow = "~lib/memory/memory.grow";
export const memory_copy = "~lib/memory/memory.copy";
export const memory_fill = "~lib/memory/memory.fill";
export const memory_data = "~lib/memory/memory.data";

// std/typedarray.ts
export const Int8Array = "~lib/typedarray/Int8Array";
Expand Down Expand Up @@ -1970,37 +1974,21 @@ function builtin_load(ctx: BuiltinContext): ExpressionRef {
) ? contextualType : type;
var arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType, Constraints.CONV_IMPLICIT);
var numOperands = operands.length;
var immOffset = numOperands >= 2 ? evaluateImmediateOffset(operands[1], compiler) : 0; // reports
if (immOffset < 0) {
compiler.currentType = outType;
return module.unreachable();
}
var immAlign: i32;
var naturalAlign = type.byteSize;
if (numOperands == 3) {
immAlign = evaluateImmediateOffset(operands[2], compiler);
if (immAlign < 0) {
var immOffset = 0;
var immAlign = type.byteSize;
if (numOperands >= 2) {
immOffset = evaluateImmediateOffset(operands[1], compiler); // reports
if (immOffset < 0) {
compiler.currentType = outType;
return module.unreachable();
}
if (immAlign > naturalAlign) {
compiler.error(
DiagnosticCode._0_must_be_a_value_between_1_and_2_inclusive,
operands[2].range, "Alignment", "0", naturalAlign.toString()
);
compiler.currentType = outType;
return module.unreachable();
}
if (!isPowerOf2(immAlign)) {
compiler.error(
DiagnosticCode._0_must_be_a_power_of_two,
operands[2].range, "Alignment"
);
compiler.currentType = outType;
return module.unreachable();
if (numOperands == 3) {
immAlign = evaluateImmediateAlign(operands[2], immAlign, compiler); // reports
if (immAlign < 0) {
compiler.currentType = outType;
return module.unreachable();
}
}
} else {
immAlign = naturalAlign;
}
compiler.currentType = outType;
return module.load(
Expand All @@ -2024,6 +2012,7 @@ function builtin_store(ctx: BuiltinContext): ExpressionRef {
checkArgsOptional(ctx, 2, 4)
) return module.unreachable();
var operands = ctx.operands;
var numOperands = operands.length;
var typeArguments = ctx.typeArguments;
var contextualType = ctx.contextualType;
var type = typeArguments![0];
Expand Down Expand Up @@ -2055,37 +2044,21 @@ function builtin_store(ctx: BuiltinContext): ExpressionRef {
);
inType = type;
}
var immOffset = operands.length >= 3 ? evaluateImmediateOffset(operands[2], compiler) : 0; // reports
if (immOffset < 0) {
compiler.currentType = Type.void;
return module.unreachable();
}
var immAlign: i32;
var naturalAlign = type.byteSize;
if (operands.length == 4) {
immAlign = evaluateImmediateOffset(operands[3], compiler);
if (immAlign < 0) {
compiler.currentType = Type.void;
return module.unreachable();
}
if (immAlign > naturalAlign) {
compiler.error(
DiagnosticCode._0_must_be_a_value_between_1_and_2_inclusive,
operands[3].range, "Alignment", "0", naturalAlign.toString()
);
var immOffset = 0;
var immAlign = type.byteSize;
if (numOperands >= 3) {
immOffset = evaluateImmediateOffset(operands[2], compiler); // reports
if (immOffset < 0) {
compiler.currentType = Type.void;
return module.unreachable();
}
if (!isPowerOf2(immAlign)) {
compiler.error(
DiagnosticCode._0_must_be_a_power_of_two,
operands[3].range, "Alignment"
);
compiler.currentType = Type.void;
return module.unreachable();
if (numOperands == 4) {
immAlign = evaluateImmediateAlign(operands[3], immAlign, compiler); // reports
if (immAlign < 0) {
compiler.currentType = Type.void;
return module.unreachable();
}
}
} else {
immAlign = naturalAlign;
}
compiler.currentType = Type.void;
return module.store(type.byteSize, arg0, arg1, inType.toNativeType(), immOffset, immAlign);
Expand Down Expand Up @@ -2555,6 +2528,122 @@ function builtin_memory_fill(ctx: BuiltinContext): ExpressionRef {
}
builtins.set(BuiltinNames.memory_fill, builtin_memory_fill);

// memory.data(size[, align]) -> usize
// memory.data<T>(values[, align]) -> usize
function builtin_memory_data(ctx: BuiltinContext): ExpressionRef {
var compiler = ctx.compiler;
var module = compiler.module;
compiler.currentType = Type.i32;
if (
checkTypeOptional(ctx) |
checkArgsOptional(ctx, 1, 2)
) return module.unreachable();
var typeArguments = ctx.typeArguments;
var operands = ctx.operands;
var numOperands = operands.length;
var usizeType = compiler.options.usizeType;
var offset: i64;
if (typeArguments !== null && typeArguments.length > 0) { // data<T>(values[, align])
let elementType = typeArguments[0];
if (!elementType.is(TypeFlags.VALUE)) {
compiler.error(
DiagnosticCode.Operation_0_cannot_be_applied_to_type_1,
ctx.reportNode.typeArgumentsRange, "memory.data", elementType.toString()
);
compiler.currentType = usizeType;
return module.unreachable();
}
let nativeElementType = elementType.toNativeType();
let valuesOperand = operands[0];
if (valuesOperand.kind != NodeKind.LITERAL || (<LiteralExpression>valuesOperand).literalKind != LiteralKind.ARRAY) {
compiler.error(
DiagnosticCode.Array_literal_expected,
operands[0].range
);
compiler.currentType = usizeType;
return module.unreachable();
}
let expressions = (<ArrayLiteralExpression>valuesOperand).elementExpressions;
let numElements = expressions.length;
let exprs = new Array<ExpressionRef>(numElements);
let isStatic = true;
for (let i = 0; i < numElements; ++i) {
let expression = expressions[i];
if (expression) {
let expr = module.precomputeExpression(
compiler.compileExpression(<Expression>expression, elementType,
Constraints.CONV_IMPLICIT | Constraints.WILL_RETAIN
)
);
if (getExpressionId(expr) == ExpressionId.Const) {
assert(getExpressionType(expr) == nativeElementType);
exprs[i] = expr;
} else {
isStatic = false;
}
} else {
exprs[i] = compiler.makeZero(elementType);
}
}
if (!isStatic) {
compiler.error(
DiagnosticCode.Expression_must_be_a_compile_time_constant,
valuesOperand.range
);
compiler.currentType = usizeType;
return module.unreachable();
}
let align = elementType.byteSize;
if (numOperands == 2) {
align = evaluateImmediateAlign(operands[1], align, compiler); // reports
if (align < 0) {
compiler.currentType = usizeType;
return module.unreachable();
}
}
let buf = new Uint8Array(numElements * elementType.byteSize);
assert(compiler.writeStaticBuffer(buf, 0, elementType, exprs) == buf.byteLength);
offset = compiler.addMemorySegment(buf, align).offset;
} else { // data(size[, align])
let arg0 = compiler.precomputeExpression(operands[0], Type.i32, Constraints.CONV_IMPLICIT);
if (getExpressionId(arg0) != ExpressionId.Const) {
compiler.error(
DiagnosticCode.Expression_must_be_a_compile_time_constant,
operands[0].range
);
compiler.currentType = usizeType;
return module.unreachable();
}
let size = getConstValueI32(arg0);
if (size < 1) {
compiler.error(
DiagnosticCode._0_must_be_a_value_between_1_and_2_inclusive,
operands[0].range, "1", i32.MAX_VALUE.toString()
);
compiler.currentType = usizeType;
return module.unreachable();
}
let align = 16;
if (numOperands == 2) {
align = evaluateImmediateAlign(operands[1], align, compiler); // reports
if (align < 0) {
compiler.currentType = usizeType;
return module.unreachable();
}
}
offset = compiler.addMemorySegment(new Uint8Array(size), align).offset;
}
// FIXME: what if recompiles happen? recompiles are bad.
compiler.currentType = usizeType;
if (usizeType == Type.usize32) {
assert(!i64_high(offset));
return module.i32(i64_low(offset));
} else {
return module.i64(i64_low(offset), i64_high(offset));
}
}
builtins.set(BuiltinNames.memory_data, builtin_memory_data);

// === Helpers ================================================================================

// changetype<T!>(value: *) -> T
Expand Down Expand Up @@ -3496,38 +3585,24 @@ function builtin_v128_load_splat(ctx: BuiltinContext): ExpressionRef {
var type = typeArguments[0];
var arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType, Constraints.CONV_IMPLICIT);
var numOperands = operands.length;
var immOffset = numOperands >= 2 ? evaluateImmediateOffset(operands[1], compiler) : 0; // reports
if (immOffset < 0) {
compiler.currentType = Type.v128;
return module.unreachable();
}
var immAlign: i32;
var naturalAlign = type.byteSize;
if (numOperands == 3) {
immAlign = evaluateImmediateOffset(operands[2], compiler);
if (immAlign < 0) {
var immOffset = 0;
var immAlign = type.byteSize;
if (numOperands >= 2) {
immOffset = evaluateImmediateOffset(operands[1], compiler); // reports
if (immOffset < 0) {
compiler.currentType = Type.v128;
return module.unreachable();
}
} else {
immAlign = naturalAlign;
if (numOperands == 3) {
immAlign = evaluateImmediateAlign(operands[2], immAlign, compiler); // reports
if (immAlign < 0) {
compiler.currentType = Type.v128;
return module.unreachable();
}
}
}
compiler.currentType = Type.v128;
if (!type.is(TypeFlags.REFERENCE)) {
if (immAlign > naturalAlign) {
compiler.error(
DiagnosticCode._0_must_be_a_value_between_1_and_2_inclusive,
operands[2].range, "Alignment", "0", naturalAlign.toString()
);
return module.unreachable();
}
if (!isPowerOf2(immAlign)) {
compiler.error(
DiagnosticCode._0_must_be_a_power_of_two,
operands[2].range, "Alignment"
);
return module.unreachable();
}
switch (type.kind) {
case TypeKind.I8:
case TypeKind.U8: {
Expand Down Expand Up @@ -3578,38 +3653,24 @@ function builtin_v128_load_ext(ctx: BuiltinContext): ExpressionRef {
var type = typeArguments[0];
var arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType, Constraints.CONV_IMPLICIT);
var numOperands = operands.length;
var immOffset = numOperands >= 2 ? evaluateImmediateOffset(operands[1], compiler) : 0; // reports
if (immOffset < 0) {
compiler.currentType = Type.v128;
return module.unreachable();
}
var immAlign: i32;
var naturalAlign = type.byteSize;
if (numOperands == 3) {
immAlign = evaluateImmediateOffset(operands[2], compiler);
if (immAlign < 0) {
var immOffset = 0;
var immAlign = type.byteSize;
if (numOperands >= 2) {
immOffset = evaluateImmediateOffset(operands[1], compiler); // reports
if (immOffset < 0) {
compiler.currentType = Type.v128;
return module.unreachable();
}
} else {
immAlign = naturalAlign;
if (numOperands == 3) {
immAlign = evaluateImmediateAlign(operands[2], immAlign, compiler); // reports
if (immAlign < 0) {
compiler.currentType = Type.v128;
return module.unreachable();
}
}
}
compiler.currentType = Type.v128;
if (!type.is(TypeFlags.REFERENCE)) {
if (immAlign > naturalAlign) {
compiler.error(
DiagnosticCode._0_must_be_a_value_between_1_and_2_inclusive,
operands[2].range, "Alignment", "0", naturalAlign.toString()
);
return module.unreachable();
}
if (!isPowerOf2(immAlign)) {
compiler.error(
DiagnosticCode._0_must_be_a_power_of_two,
operands[2].range, "Alignment"
);
return module.unreachable();
}
switch (type.kind) {
case TypeKind.I8: return module.simd_load(SIMDLoadOp.LoadI8ToI16x8, arg0, immOffset, immAlign);
case TypeKind.U8: return module.simd_load(SIMDLoadOp.LoadU8ToU16x8, arg0, immOffset, immAlign);
Expand Down Expand Up @@ -8196,6 +8257,27 @@ function evaluateImmediateOffset(expression: Expression, compiler: Compiler): i3
return value;
}

/** Evaluates a compile-time constant immediate align argument. */
function evaluateImmediateAlign(expression: Expression, naturalAlign: i32, compiler: Compiler): i32 {
var align = evaluateImmediateOffset(expression, compiler);
if (align < 0) return align;
if (align < 1 || naturalAlign > 16) {
compiler.error(
DiagnosticCode._0_must_be_a_value_between_1_and_2_inclusive,
expression.range, "Alignment", "1", naturalAlign.toString()
);
return -1;
}
if (!isPowerOf2(align)) {
compiler.error(
DiagnosticCode._0_must_be_a_power_of_two,
expression.range, "Alignment"
);
return -1;
}
return align;
}

/** Checks that the specified feature is enabled. */
function checkFeatureEnabled(ctx: BuiltinContext, feature: Feature): i32 {
var compiler = ctx.compiler;
Expand Down
Loading