Skip to content

Commit 2fa7fc7

Browse files
committed
Take advantage of smaller integer load and store ops
1 parent 5d142ba commit 2fa7fc7

File tree

13 files changed

+717
-122
lines changed

13 files changed

+717
-122
lines changed

examples/game-of-life/assembly/game-of-life.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ export function step(): void {
2121
for (var x: u32 = 0; x < w; ++x) {
2222
var xm1 = select<u32>(wm1, x - 1, x == 0),
2323
xp1 = select<u32>(0, x + 1, x == wm1);
24-
var n = load<u8>(ym1 * w + xm1) + load<u8>(ym1 * w + x) + load<u8>(ym1 * w + xp1)
25-
+ load<u8>(y * w + xm1) + load<u8>(y * w + xp1)
26-
+ load<u8>(yp1 * w + xm1) + load<u8>(yp1 * w + x) + load<u8>(yp1 * w + xp1);
24+
var n = (
25+
load<u8>(ym1 * w + xm1) + load<u8>(ym1 * w + x) + load<u8>(ym1 * w + xp1) +
26+
load<u8>(y * w + xm1) + load<u8>(y * w + xp1) +
27+
load<u8>(yp1 * w + xm1) + load<u8>(yp1 * w + x) + load<u8>(yp1 * w + xp1)
28+
);
2729
if (load<u8>(y * w + x)) {
2830
if (n < 2 || n > 3)
2931
store<u8>(s + y * w + x, 0);

src/builtins.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
13381338

13391339
// memory access
13401340

1341-
case "load": // load<T!>(offset: usize, constantOffset?: usize) -> T
1341+
case "load": // load<T!>(offset: usize, constantOffset?: usize) -> *
13421342
if (operands.length < 1 || operands.length > 2) {
13431343
if (!(typeArguments && typeArguments.length == 1))
13441344
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0");
@@ -1358,37 +1358,43 @@ export function compileCall(compiler: Compiler, prototype: FunctionPrototype, ty
13581358
offset = operands.length == 2 ? evaluateConstantOffset(compiler, operands[1]) : 0; // reports
13591359
if (offset < 0)
13601360
return module.createUnreachable();
1361-
compiler.currentType = typeArguments[0];
1362-
return module.createLoad(typeArguments[0].byteSize, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0, typeArguments[0].toNativeType(), offset);
1361+
return module.createLoad(typeArguments[0].byteSize, typeArguments[0].is(TypeFlags.SIGNED | TypeFlags.INTEGER), arg0,
1362+
typeArguments[0].is(TypeFlags.INTEGER) && contextualType.is(TypeFlags.INTEGER) && contextualType.size >= typeArguments[0].size
1363+
? (compiler.currentType = contextualType).toNativeType()
1364+
: (compiler.currentType = typeArguments[0]).toNativeType()
1365+
, offset);
13631366

1364-
case "store": // store<T?>(offset: usize, value: T, constantOffset?: usize) -> void
1367+
case "store": // store<T!>(offset: usize, value: *, constantOffset?: usize) -> void
13651368
compiler.currentType = Type.void;
13661369
if (operands.length < 2 || operands.length > 3) {
1367-
if (typeArguments && typeArguments.length != 1)
1368-
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments.length.toString(10));
1370+
if (!(typeArguments && typeArguments.length == 1))
1371+
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0");
13691372
if (operands.length < 2)
13701373
compiler.error(DiagnosticCode.Expected_at_least_0_arguments_but_got_1, reportNode.range, "2", operands.length.toString(10));
13711374
else
13721375
compiler.error(DiagnosticCode.Expected_0_arguments_but_got_1, reportNode.range, "3", operands.length.toString(10));
13731376
return module.createUnreachable();
13741377
}
1375-
if (typeArguments) {
1376-
if (typeArguments.length != 1) {
1377-
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments.length.toString(10));
1378-
return module.createUnreachable();
1379-
}
1380-
arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType);
1381-
arg1 = compiler.compileExpression(operands[1], typeArguments[0]);
1382-
} else {
1383-
arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType);
1384-
arg1 = compiler.compileExpression(operands[1], Type.i32, ConversionKind.NONE);
1378+
if (!(typeArguments && typeArguments.length == 1)) {
1379+
compiler.error(DiagnosticCode.Expected_0_type_arguments_but_got_1, reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0");
1380+
return module.createUnreachable();
13851381
}
1386-
type = compiler.currentType;
1382+
arg0 = compiler.compileExpression(operands[0], compiler.options.usizeType);
1383+
arg1 = compiler.compileExpression(operands[1], typeArguments[0],
1384+
typeArguments[0].is(TypeFlags.INTEGER)
1385+
? ConversionKind.NONE // wraps a larger integer type to a smaller one, i.e. i32.store8
1386+
: ConversionKind.IMPLICIT
1387+
);
1388+
if (compiler.currentType.is(TypeFlags.INTEGER) && typeArguments[0].is(TypeFlags.INTEGER) && typeArguments[0].size > compiler.currentType.size) {
1389+
arg1 = compiler.convertExpression(arg1, compiler.currentType, typeArguments[0], ConversionKind.IMPLICIT, operands[1]);
1390+
type = typeArguments[0];
1391+
} else
1392+
type = compiler.currentType;
13871393
offset = operands.length == 3 ? evaluateConstantOffset(compiler, operands[2]) : 0; // reports
13881394
if (offset < 0)
13891395
return module.createUnreachable();
13901396
compiler.currentType = Type.void;
1391-
return module.createStore(type.byteSize, arg0, arg1, type.toNativeType(), offset);
1397+
return module.createStore(typeArguments[0].byteSize, arg0, arg1, type.toNativeType(), offset);
13921398

13931399
case "sizeof": // sizeof<T!>() -> usize
13941400
compiler.currentType = compiler.options.usizeType;

src/compiler.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -578,15 +578,15 @@ export class Compiler extends DiagnosticEmitter {
578578
// compile statements
579579
var stmts: ExpressionRef[] | null = null;
580580
if (!instance.is(ElementFlags.DECLARED)) {
581-
declaration = assert(declaration);
581+
declaration = assert(declaration, "declaration expected");
582582
var previousFunction = this.currentFunction;
583583
this.currentFunction = instance;
584-
var statements = assert(declaration.statements);
584+
var statements = assert(declaration.statements, "implementation expected");
585585
stmts = this.compileStatements(statements);
586586
// make sure the top-level branch or all child branches return
587587
var allBranchesReturn = this.currentFunction.flow.finalize();
588588
if (instance.returnType != Type.void && !allBranchesReturn)
589-
this.error(DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value, assert(declaration.returnType).range);
589+
this.error(DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value, assert(declaration.returnType, "return type expected").range);
590590
this.currentFunction = previousFunction;
591591
}
592592

@@ -895,6 +895,7 @@ export class Compiler extends DiagnosticEmitter {
895895
this.error(DiagnosticCode.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement, statement.range);
896896
return this.module.createUnreachable();
897897
}
898+
this.currentFunction.flow.set(FlowFlags.POSSIBLY_BREAKS);
898899
return this.module.createBreak(breakLabel);
899900
}
900901

@@ -909,6 +910,7 @@ export class Compiler extends DiagnosticEmitter {
909910
this.error(DiagnosticCode.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement, statement.range);
910911
return this.module.createUnreachable();
911912
}
913+
this.currentFunction.flow.set(FlowFlags.POSSIBLY_CONTINUES);
912914
return this.module.createBreak(continueLabel);
913915
}
914916

@@ -1022,8 +1024,6 @@ export class Compiler extends DiagnosticEmitter {
10221024
}
10231025

10241026
compileReturnStatement(statement: ReturnStatement): ExpressionRef {
1025-
assert(this.currentFunction);
1026-
10271027
var expression: ExpressionRef = 0;
10281028
if (statement.value)
10291029
expression = this.compileExpression(<Expression>statement.value, this.currentFunction.returnType);
@@ -1353,7 +1353,7 @@ export class Compiler extends DiagnosticEmitter {
13531353

13541354
convertExpression(expr: ExpressionRef, fromType: Type, toType: Type, conversionKind: ConversionKind, reportNode: Node): ExpressionRef {
13551355
if (conversionKind == ConversionKind.NONE) {
1356-
assert(false);
1356+
assert(false, "concrete type expected");
13571357
return expr;
13581358
}
13591359

@@ -1437,7 +1437,7 @@ export class Compiler extends DiagnosticEmitter {
14371437

14381438
// float to void
14391439
} else {
1440-
assert(toType.flags == TypeFlags.NONE);
1440+
assert(toType.flags == TypeFlags.NONE, "void type expected");
14411441
expr = this.module.createDrop(expr);
14421442
}
14431443

@@ -2296,11 +2296,10 @@ export class Compiler extends DiagnosticEmitter {
22962296

22972297
// otherwise make use of the temp. local
22982298
else {
2299-
assert(tempLocal);
23002299
expr = this.module.createIf(
23012300
condition,
23022301
right,
2303-
this.module.createGetLocal((<Local>tempLocal).index, this.currentType.toNativeType())
2302+
this.module.createGetLocal(assert(tempLocal, "tempLocal must be set").index, this.currentType.toNativeType())
23042303
);
23052304
}
23062305
break;
@@ -2331,10 +2330,9 @@ export class Compiler extends DiagnosticEmitter {
23312330

23322331
// otherwise make use of the temp. local
23332332
else {
2334-
assert(tempLocal);
23352333
expr = this.module.createIf(
23362334
condition,
2337-
this.module.createGetLocal((<Local>tempLocal).index, this.currentType.toNativeType()),
2335+
this.module.createGetLocal(assert(tempLocal, "tempLocal must be set").index, this.currentType.toNativeType()),
23382336
right
23392337
);
23402338
}
@@ -2345,7 +2343,7 @@ export class Compiler extends DiagnosticEmitter {
23452343
throw new Error("not implemented");
23462344
}
23472345
if (possiblyOverflows && wrapSmallIntegers) {
2348-
assert(this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER));
2346+
assert(this.currentType.is(TypeFlags.SMALL | TypeFlags.INTEGER)), "small integer type expected";
23492347
expr = makeSmallIntegerWrap(expr, this.currentType, this.module);
23502348
}
23512349
return compound
@@ -2366,7 +2364,7 @@ export class Compiler extends DiagnosticEmitter {
23662364
case ElementKind.GLOBAL:
23672365
if (!this.compileGlobal(<Global>element)) // reports; not yet compiled if a static field compiled as a global
23682366
return this.module.createUnreachable();
2369-
assert((<Global>element).type != Type.void);
2367+
assert((<Global>element).type != Type.void, "concrete type expected");
23702368
// fall-through
23712369

23722370
case ElementKind.LOCAL:
@@ -2429,7 +2427,7 @@ export class Compiler extends DiagnosticEmitter {
24292427
case ElementKind.GLOBAL:
24302428
if (!this.compileGlobal(<Global>element)) // reports; not yet compiled if a static field compiled as a global
24312429
return this.module.createUnreachable();
2432-
assert((<Global>element).type != Type.void);
2430+
assert((<Global>element).type != Type.void, "concrete type expected");
24332431
this.currentType = tee ? (<Global>element).type : Type.void;
24342432
if ((<Local>element).is(ElementFlags.CONSTANT)) {
24352433
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Local>element).internalName);
@@ -2448,9 +2446,9 @@ export class Compiler extends DiagnosticEmitter {
24482446
this.error(DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, expression.range, (<Field>element).internalName);
24492447
return this.module.createUnreachable();
24502448
}
2451-
assert(resolved.targetExpression != null);
2449+
assert(resolved.targetExpression != null, "target expression expected");
24522450
targetExpr = this.compileExpression(<Expression>resolved.targetExpression, this.options.target == Target.WASM64 ? Type.usize64 : Type.usize32, ConversionKind.NONE);
2453-
assert(this.currentType.classType);
2451+
assert(this.currentType.classType, "class type expected");
24542452
this.currentType = tee ? (<Field>element).type : Type.void;
24552453
var elementNativeType = (<Field>element).type.toNativeType();
24562454
if (!tee)

src/module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ export class Module {
240240
ref: ModuleRef;
241241
lit: BinaryenLiteral;
242242

243-
static MAX_MEMORY_WASM32: Index = 0xffff;
243+
static readonly MAX_MEMORY_WASM32: Index = 0xffff;
244+
// TODO: static readonly MAX_MEMORY_WASM64
244245

245246
static create(): Module {
246247
var module = new Module();

src/program.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,10 @@ export const enum FlowFlags {
21342134
RETURNS = 1 << 0,
21352135
/** This branch possibly throws. */
21362136
POSSIBLY_THROWS = 1 << 1,
2137+
/** This branch possible breaks. */
2138+
POSSIBLY_BREAKS = 1 << 2,
2139+
/** This branch possible continues. */
2140+
POSSIBLY_CONTINUES = 1 << 3
21372141
}
21382142

21392143
/** A control flow evaluator. */
@@ -2185,17 +2189,22 @@ export class Flow {
21852189
/** Leaves the current branch or scope and returns the parent flow. */
21862190
leaveBranchOrScope(): Flow {
21872191
var parent = assert(this.parent);
2192+
2193+
// Free block-scoped locals
21882194
if (this.scopedLocals) {
21892195
for (var scopedLocal of this.scopedLocals.values())
21902196
this.currentFunction.freeTempLocal(scopedLocal);
21912197
this.scopedLocals = null;
21922198
}
2193-
// Mark parent as THROWS if any child throws
2199+
2200+
// Propagate flags to parent
21942201
if (this.is(FlowFlags.POSSIBLY_THROWS))
2195-
parent.set(FlowFlags.POSSIBLY_THROWS);
2202+
parent.set(FlowFlags.POSSIBLY_THROWS);
2203+
if (this.is(FlowFlags.POSSIBLY_BREAKS) && parent.breakLabel == this.breakLabel)
2204+
parent.set(FlowFlags.POSSIBLY_BREAKS);
2205+
if (this.is(FlowFlags.POSSIBLY_CONTINUES) && parent.continueLabel == this.continueLabel)
2206+
parent.set(FlowFlags.POSSIBLY_CONTINUES);
21962207

2197-
this.continueLabel = null;
2198-
this.breakLabel = null;
21992208
return parent;
22002209
}
22012210

std/assembly.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,9 @@ declare function sqrt<T = f32 | f64>(value: T): T;
155155
/** Rounds to the nearest integer towards zero of a 32-bit or 64-bit float. */
156156
declare function trunc<T = f32 | f64>(value: T): T;
157157
/** Loads a value of the specified type from memory. Equivalent to dereferncing a pointer in other languages. */
158-
declare function load<T>(ptr: usize, constantOffset?: usize): T;
158+
declare function load<T>(ptr: usize, constantOffset?: usize): any;
159159
/** Stores a value of the specified type to memory. Equivalent to dereferencing a pointer in other languages when assigning a value. */
160-
declare function store<T>(ptr: usize, value: T, constantOffset?: usize): void;
160+
declare function store<T>(ptr: usize, value: any, constantOffset?: usize): void;
161161
/** Returns the current memory size in units of pages. One page is 64kb. */
162162
declare function current_memory(): i32;
163163
/** Grows linear memory by a given unsigned delta of pages. One page is 64kb. Returns the previous memory size in units of pages or `-1` on failure. */

0 commit comments

Comments
 (0)