Skip to content

Commit 7de4745

Browse files
authored
feat: Implement first-class functions (#1384)
BREAKING CHANGE: Functions are now represented by a managed `Function` object, and are no longer just function table indexes under the hood. As such, returning a function reference to JS now returns a memory address. External functions imported as part of the function table can be called by index with the re-added `call_indirect` builtin, but typical AS functions must now be called idiomatically.
1 parent 8a175df commit 7de4745

File tree

68 files changed

+12096
-5932
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+12096
-5932
lines changed

src/builtins.ts

Lines changed: 217 additions & 145 deletions
Large diffs are not rendered by default.

src/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export namespace CommonNames {
191191
export const StaticArray = "StaticArray";
192192
export const Set = "Set";
193193
export const Map = "Map";
194+
export const Function = "Function";
194195
export const ArrayBufferView = "ArrayBufferView";
195196
export const ArrayBuffer = "ArrayBuffer";
196197
export const Math = "Math";

src/compiler.ts

Lines changed: 433 additions & 396 deletions
Large diffs are not rendered by default.

src/definitions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export class IDLBuilder extends ExportsWalker {
273273
// if (i >= requiredParameters) sb.push("optional ");
274274
sb.push(this.typeToString(parameters[i]));
275275
sb.push(" ");
276-
sb.push(signature.getParameterName(i));
276+
sb.push(element.getParameterName(i));
277277
}
278278
sb.push(");\n");
279279
var members = element.members;
@@ -463,7 +463,7 @@ export class TSDBuilder extends ExportsWalker {
463463
for (let i = 0; i < numParameters; ++i) {
464464
if (i) sb.push(", ");
465465
// if (i >= requiredParameters) sb.push("optional ");
466-
sb.push(signature.getParameterName(i));
466+
sb.push(element.getParameterName(i));
467467
sb.push(": ");
468468
sb.push(this.typeToString(parameters[i]));
469469
}

src/flow.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -951,12 +951,12 @@ export class Flow {
951951
for (let i = 0, k = min<i32>(numThisLocalFlags, numOtherLocalFlags); i < k; ++i) {
952952
let local = localsByIndex[i];
953953
let type = local.type;
954-
if (type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) {
954+
if (type.isShortIntegerValue) {
955955
if (before.isLocalFlag(i, LocalFlags.WRAPPED) && !after.isLocalFlag(i, LocalFlags.WRAPPED)) {
956956
return true;
957957
}
958958
}
959-
if (type.is(TypeFlags.REFERENCE)) {
959+
if (type.isNullableReference) {
960960
if (before.isLocalFlag(i, LocalFlags.NONNULL) && !after.isLocalFlag(i, LocalFlags.NONNULL)) {
961961
return true;
962962
}
@@ -986,19 +986,19 @@ export class Flow {
986986

987987
/** Checks if an expression of the specified type is known to be non-null, even if the type might be nullable. */
988988
isNonnull(expr: ExpressionRef, type: Type): bool {
989-
if (!type.is(TypeFlags.NULLABLE)) return true;
989+
if (!type.isNullableReference) return true;
990990
// below, only teeLocal/getLocal are relevant because these are the only expressions that
991991
// depend on a dynamic nullable state (flag = LocalFlags.NONNULL), while everything else
992992
// has already been handled by the nullable type check above.
993993
switch (getExpressionId(expr)) {
994994
case ExpressionId.LocalSet: {
995995
if (!isLocalTee(expr)) break;
996996
let local = this.parentFunction.localsByIndex[getLocalSetIndex(expr)];
997-
return !local.type.is(TypeFlags.NULLABLE) || this.isLocalFlag(local.index, LocalFlags.NONNULL, false);
997+
return !local.type.isNullableReference || this.isLocalFlag(local.index, LocalFlags.NONNULL, false);
998998
}
999999
case ExpressionId.LocalGet: {
10001000
let local = this.parentFunction.localsByIndex[getLocalGetIndex(expr)];
1001-
return !local.type.is(TypeFlags.NULLABLE) || this.isLocalFlag(local.index, LocalFlags.NONNULL, false);
1001+
return !local.type.isNullableReference || this.isLocalFlag(local.index, LocalFlags.NONNULL, false);
10021002
}
10031003
}
10041004
return false;
@@ -1219,7 +1219,7 @@ export class Flow {
12191219
assert(type != Type.void);
12201220

12211221
// types other than i8, u8, i16, u16 and bool do not overflow
1222-
if (!type.is(TypeFlags.SHORT | TypeFlags.INTEGER)) return false;
1222+
if (!type.isShortIntegerValue) return false;
12231223

12241224
var operand: ExpressionRef;
12251225
switch (getExpressionId(expr)) {
@@ -1347,7 +1347,7 @@ export class Flow {
13471347
// wrapped, it can't overflow.
13481348
case BinaryOp.ShrU32: {
13491349
let shift = 32 - type.size;
1350-
return type.is(TypeFlags.SIGNED)
1350+
return type.isSignedIntegerValue
13511351
? !(
13521352
getExpressionId(operand = getBinaryRight(expr)) == ExpressionId.Const &&
13531353
getConstValueI32(operand) > shift // must clear MSB
@@ -1492,9 +1492,11 @@ export class Flow {
14921492

14931493
/** Tests if a conversion from one type to another can technically overflow. */
14941494
function canConversionOverflow(fromType: Type, toType: Type): bool {
1495-
return !fromType.is(TypeFlags.INTEGER) // non-i32 locals or returns
1496-
|| fromType.size > toType.size
1497-
|| fromType.is(TypeFlags.SIGNED) != toType.is(TypeFlags.SIGNED);
1495+
return toType.isShortIntegerValue && (
1496+
!fromType.isIntegerValue || // i.e. float to small int
1497+
fromType.size > toType.size || // larger int to small int
1498+
fromType.isSignedIntegerValue != toType.isSignedIntegerValue // signedness mismatch
1499+
);
14981500
}
14991501

15001502
/** Finds all indexes of locals used in the specified expression. */

src/glue/js/i64.d.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ declare type i64 = { __Long__: true }; // opaque
88
declare const i64_zero: i64;
99
declare const i64_one: i64;
1010

11+
declare function i64_is(value: unknown): value is i64;
1112
declare function i64_new(lo: i32, hi?: i32): i64;
1213
declare function i64_low(value: i64): i32;
1314
declare function i64_high(value: i64): i32;
@@ -28,22 +29,22 @@ declare function i64_shr(left: i64, right: i64): i64;
2829
declare function i64_shr_u(left: i64, right: i64): i64;
2930
declare function i64_not(value: i64): i64;
3031

31-
declare function i64_eq(left: i64, right: i64): bool;
32-
declare function i64_ne(left: i64, right: i64): bool;
33-
declare function i64_gt(left: i64, right: i64): bool;
32+
declare function i64_eq(left: i64, right: i64): boolean;
33+
declare function i64_ne(left: i64, right: i64): boolean;
34+
declare function i64_gt(left: i64, right: i64): boolean;
3435

3536
declare function i64_align(value: i64, alignment: i32): i64;
3637

37-
declare function i64_is_i8(value: i64): bool;
38-
declare function i64_is_i16(value: i64): bool;
39-
declare function i64_is_i32(value: i64): bool;
40-
declare function i64_is_u8(value: i64): bool;
41-
declare function i64_is_u16(value: i64): bool;
42-
declare function i64_is_u32(value: i64): bool;
43-
declare function i64_is_bool(value: i64): bool;
44-
declare function i64_is_f32(value: i64): bool;
45-
declare function i64_is_f64(value: i64): bool;
38+
declare function i64_is_i8(value: i64): boolean;
39+
declare function i64_is_i16(value: i64): boolean;
40+
declare function i64_is_i32(value: i64): boolean;
41+
declare function i64_is_u8(value: i64): boolean;
42+
declare function i64_is_u16(value: i64): boolean;
43+
declare function i64_is_u32(value: i64): boolean;
44+
declare function i64_is_bool(value: i64): boolean;
45+
declare function i64_is_f32(value: i64): boolean;
46+
declare function i64_is_f64(value: i64): boolean;
4647

4748
declare function i64_to_f32(value: i64): f64;
4849
declare function i64_to_f64(value: i64): f64;
49-
declare function i64_to_string(value: i64, unsigned?: bool): string;
50+
declare function i64_to_string(value: i64, unsigned?: boolean): string;

src/glue/js/i64.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ global.i64_zero = Long.ZERO;
1111
global.i64_one = Long.ONE;
1212
global.i64_neg_one = Long.fromInt(-1);
1313

14+
global.i64_is = function i64_is(value) {
15+
return Long.isLong(value);
16+
};
17+
1418
global.i64_new = function i64_new(lo, hi) {
1519
return Long.fromBits(lo, hi);
1620
};

src/glue/wasm/i64.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
/* eslint-disable @typescript-eslint/no-unused-vars */
77

8+
// @ts-ignore: decorator
89
@global const i64_zero: i64 = 0;
910

1011
// @ts-ignore: decorator
@@ -13,6 +14,12 @@
1314
// @ts-ignore: decorator
1415
@global const i64_neg_one: i64 = -1;
1516

17+
// @ts-ignore: decorator
18+
@global @inline
19+
function i64_is<T>(value: T): bool {
20+
return isInteger<T>() && sizeof<T>() == 8;
21+
}
22+
1623
// @ts-ignore: decorator
1724
@global @inline
1825
function i64_new(lo: i32, hi: i32 = 0): i64 {

src/parser.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,13 +1563,14 @@ export class Parser extends DiagnosticEmitter {
15631563
var parameters = this.parseParameters(tn);
15641564
if (!parameters) return null;
15651565

1566-
return this.parseFunctionExpressionCommon(tn, name, parameters, arrowKind, startPos, signatureStart);
1566+
return this.parseFunctionExpressionCommon(tn, name, parameters, this.parseParametersThis, arrowKind, startPos, signatureStart);
15671567
}
15681568

15691569
private parseFunctionExpressionCommon(
15701570
tn: Tokenizer,
15711571
name: IdentifierExpression,
15721572
parameters: ParameterNode[],
1573+
explicitThis: NamedTypeNode | null,
15731574
arrowKind: ArrowKind,
15741575
startPos: i32 = -1,
15751576
signatureStart: i32 = -1
@@ -1598,7 +1599,7 @@ export class Parser extends DiagnosticEmitter {
15981599
var signature = Node.createFunctionType(
15991600
parameters,
16001601
returnType,
1601-
null, // TODO?
1602+
explicitThis,
16021603
false,
16031604
tn.range(signatureStart, tn.pos)
16041605
);
@@ -3585,6 +3586,7 @@ export class Parser extends DiagnosticEmitter {
35853586
tn,
35863587
Node.createEmptyIdentifierExpression(tn.range(startPos)),
35873588
[],
3589+
null,
35883590
ArrowKind.ARROW_PARENTHESIZED
35893591
);
35903592
}
@@ -3777,6 +3779,7 @@ export class Parser extends DiagnosticEmitter {
37773779
identifier.range
37783780
)
37793781
],
3782+
null,
37803783
ArrowKind.ARROW_SINGLE,
37813784
startPos
37823785
);

0 commit comments

Comments
 (0)