Skip to content

Commit 0228ab9

Browse files
committed
Minor restructure and fixes; README; Proposed binaryen additions
1 parent 5ff88e1 commit 0228ab9

13 files changed

+388
-69
lines changed

README.md

+26-40
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,34 @@ AssemblyScript NEXT
33

44
[![Build Status](https://travis-ci.org/AssemblyScript/next.svg?branch=master)](https://travis-ci.org/AssemblyScript/next)
55

6-
This repository contains compiler components for the next iteration of the AssemblyScript compiler written in AssemblyScript itself.
6+
**AssemblyScript** is a new compiler targeting WebAssembly while utilizing [TypeScript](http://www.typescriptlang.org)'s syntax and [node](https://nodejs.org)'s vibrant ecosystem. Instead of requiring complex toolchains to set up, you can simply `npm install` it - or run it in a browser.
77

8-
Note that the code uses some features and standard library components that are not yet supported by any version of asc. To account for this, the code has been written in "portable AssemblyScript", a TypeScript-compatible subset of a subset of a superset of JavaScript, that also compiles to JavaScript using TSC.
8+
By compiling a variant of TypeScript to [Binaryen](https://github.com/WebAssembly/binaryen) IR, the resulting module can be validated, optimized, emitted in WebAssembly text or binary format and converted to [asm.js](http://asmjs.org) as a polyfill.
99

10-
Why is this necessary?
11-
----------------------
10+
The compiler itself is written in "portable AssemblyScript" so it can be compiled to both JavaScript using `tsc` and, eventually, to WebAssembly using `asc`.
1211

13-
Well, it isn't, but: In order to be able to compile the AssemblyScript compiler itself to WebAssembly eventually, we cannot depend on TypeScript because it is written in vanilla TypeScript and makes use of quite a few non-AOT-compatible dynamic features of JavaScript.
14-
15-
Cons:
16-
- A lot of work
17-
- Dealing with TypeScript compatibility issues
18-
19-
Pros:
20-
- One day compiling to WebAssembly for performance
21-
- Necessary features only, reducing binary size
22-
- Linking against Binaryen compiled to WebAssembly, reducing overhead
23-
24-
Side effects:
25-
- Good fire test for the compiler
26-
- Good benchmark when comparing both versions
27-
- Benefits standard library design ideas
28-
29-
How does it work?
30-
-----------------
12+
Development status
13+
------------------
3114

32-
AssemblyScript NEXT compiles a subset (or variant) of TypeScript to [Binaryen](https://github.com/WebAssembly/binaryen) IR. The resulting module can then be optimized, emitted in text or binary format or converted to [asm.js](http://asmjs.org) as a polyfill.
15+
This version of the compiler (0.5.0, NEXT) is relatively new and does not yet support some features a TypeScript programmer might expect, e.g., strings, arrays and classes. For now, you can see the [compiler tests](https://github.com/AssemblyScript/next/tree/master/tests/compiler) for an overview of what's supposed to be working already.
3316

3417
Getting started
3518
---------------
3619

37-
If you'd like to try out NEXT today or even plan to contribute, this is how you do it:
20+
If you'd like to try it today or even plan to contribute, this is how you do it:
3821

3922
```
4023
$> git clone https://github.com/AssemblyScript/next.git
4124
$> cd next
4225
$> npm install
43-
$> node bin\asc yourModule.ts
4426
```
4527

46-
Building an UMD bundle to `dist/assemblyscript.js` (does not bundle [binaryen.js](https://github.com/AssemblyScript/binaryen.js)):
28+
Author your module in AssemblyScript ([definitions](./assembly.d.ts)) or portable AssemblyScript ([definitions](./portable-assembly.d.ts)) and run:
4729

4830
```
49-
$> npm run build
50-
```
51-
52-
Running the [tests](./tests):
53-
54-
```
55-
$> npm test
31+
$> node bin\asc yourModule.ts
5632
```
5733

58-
Development status
59-
------------------
60-
61-
For now, see the [compiler tests](https://github.com/AssemblyScript/next/tree/master/tests/compiler) for an overview of what's supposed to be working already.
62-
6334
Using the CLI
6435
-------------
6536

@@ -82,9 +53,24 @@ Options:
8253
--noTreeShaking Disables tree-shaking.
8354
--noDebug Disables assertions.
8455
--trapMode Sets the trap mode to use.
85-
none Do not modify trapping operations. This is the default.
56+
allow Allow trapping operations. This is the default.
8657
clamp Replace trapping operations with clamping semantics.
8758
js Replace trapping operations with JS semantics.
8859
```
8960

90-
Unless a bundle has been built to `dist/`, `asc` runs the TypeScript sources directly via [ts-node](https://www.npmjs.com/package/ts-node). Useful for development.
61+
Unless a bundle has been built to `dist/`, `asc` runs the TypeScript sources on the fly via [ts-node](https://www.npmjs.com/package/ts-node). Useful for development.
62+
63+
Building
64+
--------
65+
66+
Building an UMD bundle to `dist/assemblyscript.js` (does not bundle [binaryen.js](https://github.com/AssemblyScript/binaryen.js)):
67+
68+
```
69+
$> npm run build
70+
```
71+
72+
Running the [tests](./tests):
73+
74+
```
75+
$> npm test
76+
```

assembly.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ declare function isFinite<T = f32 | f64>(value: T): bool;
8989
/** Traps if the specified value evaluates to `false`. */
9090
declare function assert(isTrue: bool): void;
9191

92-
// Internal decorators
92+
// Internal decorators (not yet implemented)
9393

9494
/** Annotates an element being part of the global namespace. */
9595
declare function global(): any;
@@ -98,7 +98,7 @@ declare function inline(): any;
9898
/** Annotates a class using a C-style memory layout. */
9999
declare function struct(): any;
100100

101-
// Standard library
101+
// Standard library (not yet implemented)
102102

103103
/// <reference path="./std/carray.d.ts" />
104104
/// <reference path="./std/cstring.d.ts" />

bin/asc.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ if (args.help || args._.length < 1) {
6666
"",
6767
"Examples: asc hello.ts",
6868
" asc hello.ts -b hello.wasm -t hello.wast -a hello.js",
69-
" asc hello.ts -b > hello.wasm",
69+
" asc hello1.ts hello2.ts -b -O > hello.wasm",
7070
"",
7171
"Options:"
7272
].concat(options).join("\n"));
@@ -145,6 +145,10 @@ if (args.trapMode === "clamp")
145145
module.runPasses([ "trap-mode-clamp" ]);
146146
else if (args.trapMode === "js")
147147
module.runPasses([ "trap-mode-js" ]);
148+
else if (args.trapMode !== "allow") {
149+
console.log("Unsupported trap mode: " + args.trapMode);
150+
process.exit(1);
151+
}
148152

149153
if (args.optimize)
150154
module.optimize();

bin/asc.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@
5050
"trapMode": {
5151
"desc": [
5252
"Sets the trap mode to use.",
53-
"none Do not modify trapping operations. This is the default.",
53+
"allow Allow trapping operations. This is the default.",
5454
"clamp Replace trapping operations with clamping semantics.",
5555
"js Replace trapping operations with JS semantics."
5656
],
5757
"type": "string",
58-
"default": "none"
58+
"default": "allow"
5959
}
6060
}

index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./src";

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require("./dist/assemblyscript");

package.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"typescript": "^2.6.2",
3232
"webpack": "^3.10.0"
3333
},
34-
"main": "dist/assemblyscript.js",
34+
"main": "index.js",
35+
"types": "index.d.ts",
3536
"bin": {
3637
"asc": "bin/asc.js"
3738
},
@@ -48,10 +49,15 @@
4849
"bin/asc.json",
4950
"dist/assemblyscript.js",
5051
"dist/assemblyscript.js.map",
52+
"index.d.ts",
53+
"index.js",
5154
"LICENSE",
5255
"NOTICE",
5356
"package.json",
5457
"package-lock.json",
55-
"README.md"
58+
"portable-assembly.d.ts",
59+
"portable-assembly.js",
60+
"README.md",
61+
"src"
5662
]
5763
}

portable-assembly.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
// Portable types
44

5-
// Note that semantics differences require additional explicit conversions for full compatibility.
5+
// Note that semantic differences require additional explicit conversions for full compatibility.
66
// For example, when casting an i32 to an u8, doing `<u8>(someI32 & 0xff)` will yield the same
77
// result when compiling to WebAssembly or JS while `<u8>someI32` alone does nothing in JS.
88

src/compiler.ts

+128-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,56 @@
11
import { compileCall as compileBuiltinCall, initialize } from "./builtins";
22
import { PATH_DELIMITER } from "./constants";
33
import { DiagnosticCode, DiagnosticEmitter } from "./diagnostics";
4-
import { Module, MemorySegment, ExpressionRef, UnaryOp, BinaryOp, NativeType, FunctionRef, FunctionTypeRef, getExpressionId, ExpressionId, getExpressionType, getFunctionBody, getConstValueI32, getConstValueI64Low, getConstValueI64High, getConstValueF32, getConstValueF64 } from "./module";
5-
import { Program, ClassPrototype, Class, Element, ElementKind, Enum, FunctionPrototype, Function, Global, Local, Namespace, Parameter, EnumValue } from "./program";
4+
import {
5+
6+
Module,
7+
MemorySegment,
8+
ExpressionRef,
9+
UnaryOp,
10+
BinaryOp,
11+
NativeType,
12+
FunctionTypeRef,
13+
FunctionRef,
14+
ExpressionId,
15+
16+
getExpressionId,
17+
getExpressionType,
18+
getFunctionBody,
19+
getConstValueI32,
20+
getConstValueI64Low,
21+
getConstValueI64High,
22+
getConstValueF32,
23+
getConstValueF64,
24+
getGetLocalIndex,
25+
getGetGlobalName,
26+
isLoadAtomic,
27+
isLoadSigned,
28+
getLoadBytes,
29+
getLoadOffset,
30+
getLoadPtr,
31+
getUnaryOp,
32+
getUnaryValue,
33+
getBinaryOp,
34+
getBinaryLeft,
35+
getBinaryRight
36+
37+
} from "./module";
38+
import {
39+
40+
Program,
41+
ClassPrototype,
42+
Class, Element,
43+
ElementKind,
44+
Enum,
45+
FunctionPrototype,
46+
Function,
47+
Global,
48+
Local,
49+
Namespace,
50+
Parameter,
51+
EnumValue
52+
53+
} from "./program";
654
import { I64, U64, sb } from "./util";
755
import { Token } from "./tokenizer";
856
import {
@@ -65,6 +113,7 @@ import {
65113
UnaryPostfixExpression,
66114
UnaryPrefixExpression,
67115

116+
// utility
68117
hasModifier
69118

70119
} from "./ast";
@@ -1021,7 +1070,8 @@ export class Compiler extends DiagnosticEmitter {
10211070
this.module.runPasses([ "precompute" ], funcRef);
10221071
const ret: ExpressionRef = getFunctionBody(funcRef);
10231072
this.module.removeFunction("__precompute");
1024-
// TODO: also remove the function type somehow if no longer used
1073+
// TODO: also remove the function type somehow if no longer used or make the C-API accept
1074+
// a `null` typeRef, using an implicit type.
10251075
return ret;
10261076
}
10271077

@@ -1190,6 +1240,41 @@ export class Compiler extends DiagnosticEmitter {
11901240
return expr;
11911241
}
11921242

1243+
cloneExpressionRef(expr: ExpressionRef, noSideEffects: bool = false, maxDepth: i32 = 0x7fffffff): ExpressionRef {
1244+
// currently supports side effect free expressions only
1245+
if (maxDepth < 0)
1246+
return 0;
1247+
let nested1: ExpressionRef,
1248+
nested2: ExpressionRef;
1249+
switch (getExpressionId(expr)) {
1250+
case ExpressionId.Const:
1251+
switch (getExpressionType(expr)) {
1252+
case NativeType.I32: return this.module.createI32(getConstValueI32(expr));
1253+
case NativeType.I64: return this.module.createI64(getConstValueI64Low(expr), getConstValueI64High(expr));
1254+
case NativeType.F32: return this.module.createF32(getConstValueF32(expr));
1255+
case NativeType.F64: return this.module.createF64(getConstValueF64(expr));
1256+
default: throw new Error("unexpected expression type");
1257+
}
1258+
case ExpressionId.GetLocal:
1259+
return this.module.createGetLocal(getGetLocalIndex(expr), getExpressionType(expr));
1260+
case ExpressionId.GetGlobal:
1261+
return this.module.createGetGlobal(getGetGlobalName(expr), getExpressionType(expr));
1262+
case ExpressionId.Load:
1263+
if (!(nested1 = this.cloneExpressionRef(getLoadPtr(expr), noSideEffects, maxDepth - 1))) break;
1264+
return isLoadAtomic(expr)
1265+
? this.module.createAtomicLoad(getLoadBytes(expr), nested1, getExpressionType(expr), getLoadOffset(expr))
1266+
: this.module.createLoad(getLoadBytes(expr), isLoadSigned(expr), nested1, getExpressionType(expr), getLoadOffset(expr));
1267+
case ExpressionId.Unary:
1268+
if (!(nested1 = this.cloneExpressionRef(getUnaryValue(expr), noSideEffects, maxDepth - 1))) break;
1269+
return this.module.createUnary(getUnaryOp(expr), nested1);
1270+
case ExpressionId.Binary:
1271+
if (!(nested1 = this.cloneExpressionRef(getBinaryLeft(expr), noSideEffects, maxDepth - 1))) break;
1272+
if (!(nested2 = this.cloneExpressionRef(getBinaryLeft(expr), noSideEffects, maxDepth - 1))) break;
1273+
return this.module.createBinary(getBinaryOp(expr), nested1, nested2);
1274+
}
1275+
return 0;
1276+
}
1277+
11931278
compileAssertionExpression(expression: AssertionExpression, contextualType: Type): ExpressionRef {
11941279
const toType: Type | null = this.program.resolveType(expression.toType, this.currentFunction.contextualTypeArguments); // reports
11951280
return toType && toType != contextualType
@@ -1426,15 +1511,31 @@ export class Compiler extends DiagnosticEmitter {
14261511
case Token.AMPERSAND_AMPERSAND: // left && right
14271512
left = this.compileExpression(expression.left, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
14281513
right = this.compileExpression(expression.right, this.currentType);
1429-
// TODO: once it's possible to clone 'left', we could check if it is a Const, GetLocal, GetGlobal or Load and avoid the tempLocal
1514+
1515+
// simplify if left is free of side effects while tolerating two levels of nesting, e.g., i32.load(i32.load(i32.const))
1516+
// if (condition = this.cloneExpressionRef(left, true, 2))
1517+
// return this.module.createIf(
1518+
// this.currentType.isLongInteger
1519+
// ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
1520+
// : this.currentType == Type.f64
1521+
// ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
1522+
// : this.currentType == Type.f32
1523+
// ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
1524+
// : condition, // usual case: saves one EQZ when not using EQZ above
1525+
// right,
1526+
// left
1527+
// );
1528+
1529+
// otherwise use a temporary local for the intermediate value
14301530
tempLocal = this.currentFunction.addLocal(this.currentType);
1531+
condition = this.module.createTeeLocal(tempLocal.index, left);
14311532
return this.module.createIf(
14321533
this.currentType.isLongInteger
1433-
? this.module.createBinary(BinaryOp.NeI64, this.module.createTeeLocal(tempLocal.index, left), this.module.createI64(0, 0))
1534+
? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
14341535
: this.currentType == Type.f64
1435-
? this.module.createBinary(BinaryOp.NeF64, this.module.createTeeLocal(tempLocal.index, left), this.module.createF64(0))
1536+
? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
14361537
: this.currentType == Type.f32
1437-
? this.module.createBinary(BinaryOp.NeF32, this.module.createTeeLocal(tempLocal.index, left), this.module.createF32(0))
1538+
? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
14381539
: this.module.createTeeLocal(tempLocal.index, left),
14391540
right,
14401541
this.module.createGetLocal(tempLocal.index, typeToNativeType(tempLocal.type))
@@ -1443,15 +1544,31 @@ export class Compiler extends DiagnosticEmitter {
14431544
case Token.BAR_BAR: // left || right
14441545
left = this.compileExpression(expression.left, contextualType, contextualType == Type.void ? ConversionKind.NONE : ConversionKind.IMPLICIT);
14451546
right = this.compileExpression(expression.right, this.currentType);
1446-
// TODO: same as above
1547+
1548+
// simplify if left is free of side effects while tolerating two levels of nesting
1549+
// if (condition = this.cloneExpressionRef(left, true, 2))
1550+
// return this.module.createIf(
1551+
// this.currentType.isLongInteger
1552+
// ? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
1553+
// : this.currentType == Type.f64
1554+
// ? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
1555+
// : this.currentType == Type.f32
1556+
// ? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
1557+
// : condition, // usual case: saves one EQZ when not using EQZ above
1558+
// left,
1559+
// right
1560+
// );
1561+
1562+
// otherwise use a temporary local for the intermediate value
14471563
tempLocal = this.currentFunction.addLocal(this.currentType);
1564+
condition = this.module.createTeeLocal(tempLocal.index, left);
14481565
return this.module.createIf(
14491566
this.currentType.isLongInteger
1450-
? this.module.createBinary(BinaryOp.NeI64, this.module.createTeeLocal(tempLocal.index, left), this.module.createI64(0, 0))
1567+
? this.module.createBinary(BinaryOp.NeI64, condition, this.module.createI64(0, 0))
14511568
: this.currentType == Type.f64
1452-
? this.module.createBinary(BinaryOp.NeF64, this.module.createTeeLocal(tempLocal.index, left), this.module.createF64(0))
1569+
? this.module.createBinary(BinaryOp.NeF64, condition, this.module.createF64(0))
14531570
: this.currentType == Type.f32
1454-
? this.module.createBinary(BinaryOp.NeF32, this.module.createTeeLocal(tempLocal.index, left), this.module.createF32(0))
1571+
? this.module.createBinary(BinaryOp.NeF32, condition, this.module.createF32(0))
14551572
: this.module.createTeeLocal(tempLocal.index, left),
14561573
this.module.createGetLocal(tempLocal.index, typeToNativeType(tempLocal.type)),
14571574
right

0 commit comments

Comments
 (0)