1
0
mirror of https://github.com/fluencelabs/assemblyscript synced 2025-06-29 14:41:52 +00:00

Use long.js in JS and native i64 in WASM; Compile literals more thoroughly

This commit is contained in:
dcodeIO
2018-02-14 09:18:43 +01:00
parent 874f87f478
commit b1c6ccab2a
82 changed files with 1753 additions and 2199 deletions

@ -10,10 +10,6 @@ import {
Range
} from "./tokenizer";
import {
I64
} from "./util/i64";
import {
normalize as normalizePath,
resolve as resolvePath

@ -43,6 +43,8 @@ import {
Flow,
FlowFlags,
ElementFlags,
ConstantValueKind,
PATH_DELIMITER,
LIBRARY_PREFIX
} from "./program";
@ -120,11 +122,6 @@ import {
typesToNativeTypes
} from "./types";
import {
I64,
U64
} from "./util/i64";
import {
sb
} from "./util/sb";
@ -198,7 +195,7 @@ export class Compiler extends DiagnosticEmitter {
currentType: Type = Type.void;
/** Counting memory offset. */
memoryOffset: U64 = new U64(8, 0); // leave space for (any size of) NULL
memoryOffset: I64;
/** Memory segments being compiled. */
memorySegments: MemorySegment[] = new Array();
/** Map of already compiled static string segments. */
@ -217,7 +214,7 @@ export class Compiler extends DiagnosticEmitter {
super(program.diagnostics);
this.program = program;
this.options = options ? options : new Options();
this.memoryOffset = new U64(this.options.usizeType.byteSize); // leave space for `null`
this.memoryOffset = i64_new(this.options.usizeType.byteSize, 0); // leave space for `null`
this.module = Module.create();
}
@ -255,20 +252,16 @@ export class Compiler extends DiagnosticEmitter {
// set up static memory segments and the heap base pointer
if (!this.options.noMemory) {
var initial = this.memoryOffset.clone();
var alignMask = this.options.usizeType.byteSize - 1;
initial.add32(alignMask); // align to 4/8 bytes
initial.and32(~alignMask, ~0);
var memoryOffset = this.memoryOffset;
this.memoryOffset = memoryOffset = i64_align(memoryOffset, this.options.usizeType.byteSize);
if (this.options.target == Target.WASM64)
this.module.addGlobal("HEAP_BASE", NativeType.I64, false, this.module.createI64(initial.lo, initial.hi));
this.module.addGlobal("HEAP_BASE", NativeType.I64, false, this.module.createI64(i64_low(memoryOffset), i64_high(memoryOffset)));
else
this.module.addGlobal("HEAP_BASE", NativeType.I32, false, this.module.createI32(initial.lo));
this.module.addGlobal("HEAP_BASE", NativeType.I32, false, this.module.createI32(i64_low(memoryOffset)));
// determine initial page size
initial.add32(0xffff); // align to page size
initial.and32(~0xffff, ~0);
initial.shru32(16); // ^= number of pages
this.module.setMemory(initial.toI32(), Module.MAX_MEMORY_WASM32 /* TODO: not WASM64 compatible yet */, this.memorySegments, this.options.target, "memory");
var pages = i64_shr_u(i64_align(memoryOffset, 0x10000), i64_new(16, 0));
this.module.setMemory(i64_low(pages), Module.MAX_MEMORY_WASM32 /* TODO: not WASM64 compatible yet */, this.memorySegments, this.options.target, "memory");
}
return this.module;
}
@ -458,18 +451,22 @@ export class Compiler extends DiagnosticEmitter {
switch (exprType) {
case NativeType.I32:
global.constantIntegerValue = new I64(_BinaryenConstGetValueI32(initExpr), 0);
global.constantValueKind = ConstantValueKind.INTEGER;
global.constantIntegerValue = i64_new(_BinaryenConstGetValueI32(initExpr), 0);
break;
case NativeType.I64:
global.constantIntegerValue = new I64(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr));
global.constantValueKind = ConstantValueKind.INTEGER;
global.constantIntegerValue = i64_new(_BinaryenConstGetValueI64Low(initExpr), _BinaryenConstGetValueI64High(initExpr));
break;
case NativeType.F32:
global.constantValueKind = ConstantValueKind.FLOAT;
global.constantFloatValue = _BinaryenConstGetValueF32(initExpr);
break;
case NativeType.F64:
global.constantValueKind = ConstantValueKind.FLOAT;
global.constantFloatValue = _BinaryenConstGetValueF64(initExpr);
break;
@ -819,14 +816,11 @@ export class Compiler extends DiagnosticEmitter {
// memory
/** Adds a static memory segment with the specified data. */
addMemorySegment(buffer: Uint8Array): MemorySegment {
if (this.memoryOffset.lo & 7) { // align to 8 bytes so any native data type is aligned here
this.memoryOffset.or32(7);
this.memoryOffset.add32(1);
}
var segment = MemorySegment.create(buffer, this.memoryOffset.clone());
addMemorySegment(buffer: Uint8Array, alignment: i32 = 8): MemorySegment {
var memoryOffset = i64_align(this.memoryOffset, alignment);
var segment = MemorySegment.create(buffer, memoryOffset);
this.memorySegments.push(segment);
this.memoryOffset.add32(buffer.length);
this.memoryOffset = i64_add(memoryOffset, i64_new(buffer.length, 0));
return segment;
}
@ -1315,7 +1309,7 @@ export class Compiler extends DiagnosticEmitter {
compileInlineConstant(element: VariableLikeElement, contextualType: Type): ExpressionRef {
assert(element.is(ElementFlags.INLINED));
switch (element.type.is(TypeFlags.INTEGER) && contextualType.is(TypeFlags.INTEGER) && element.type.size <= contextualType.size
switch (element.type.is(TypeFlags.INTEGER) && contextualType.is(TypeFlags.INTEGER) && element.type.size < contextualType.size
? (this.currentType = contextualType).kind // essentially precomputes a (sign-)extension
: (this.currentType = element.type).kind
) {
@ -1323,32 +1317,32 @@ export class Compiler extends DiagnosticEmitter {
case TypeKind.I8:
case TypeKind.I16:
var shift = element.type.computeSmallIntegerShift(Type.i32);
return this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() << shift >> shift : 0);
return this.module.createI32(element.constantValueKind == ConstantValueKind.INTEGER ? i64_low(element.constantIntegerValue) << shift >> shift : 0);
case TypeKind.U8:
case TypeKind.U16:
case TypeKind.BOOL:
var mask = element.type.computeSmallIntegerMask(Type.i32);
return this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.toI32() & mask : 0);
return this.module.createI32(element.constantValueKind == ConstantValueKind.INTEGER ? i64_low(element.constantIntegerValue) & mask : 0);
case TypeKind.I32:
case TypeKind.U32:
return this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.lo : 0)
return this.module.createI32(element.constantValueKind == ConstantValueKind.INTEGER ? i64_low(element.constantIntegerValue) : 0)
case TypeKind.ISIZE:
case TypeKind.USIZE:
if (!element.program.options.isWasm64)
return this.module.createI32(element.constantIntegerValue ? element.constantIntegerValue.lo : 0)
return this.module.createI32(element.constantValueKind == ConstantValueKind.INTEGER ? i64_low(element.constantIntegerValue) : 0)
// fall-through
case TypeKind.I64:
case TypeKind.U64:
return element.constantIntegerValue
? this.module.createI64(element.constantIntegerValue.lo, element.constantIntegerValue.hi)
return element.constantValueKind == ConstantValueKind.INTEGER
? this.module.createI64(i64_low(element.constantIntegerValue), i64_high(element.constantIntegerValue))
: this.module.createI64(0);
case TypeKind.F32:
return this.module.createF32((<VariableLikeElement>element).constantFloatValue);
return this.module.createF32((<VariableLikeElement>element).constantFloatValue); // safe because it's a 'number' in JS
case TypeKind.F64:
return this.module.createF64((<VariableLikeElement>element).constantFloatValue);
@ -2889,10 +2883,11 @@ export class Compiler extends DiagnosticEmitter {
return this.module.createUnreachable();
}
compileLiteralExpression(expression: LiteralExpression, contextualType: Type): ExpressionRef {
compileLiteralExpression(expression: LiteralExpression, contextualType: Type, implicitNegate: bool = false): ExpressionRef {
switch (expression.literalKind) {
case LiteralKind.ARRAY:
assert(!implicitNegate);
var classType = contextualType.classType;
if (classType && classType == this.program.elements.get("Array") && classType.typeArguments && classType.typeArguments.length == 1)
return this.compileStaticArray(classType.typeArguments[0], (<ArrayLiteralExpression>expression).elementExpressions);
@ -2901,6 +2896,8 @@ export class Compiler extends DiagnosticEmitter {
case LiteralKind.FLOAT: {
var floatValue = (<FloatLiteralExpression>expression).value;
if (implicitNegate)
floatValue = -floatValue;
if (contextualType == Type.f32)
return this.module.createF32(<f32>floatValue);
this.currentType = Type.f64;
@ -2909,31 +2906,97 @@ export class Compiler extends DiagnosticEmitter {
case LiteralKind.INTEGER:
var intValue = (<IntegerLiteralExpression>expression).value;
if (contextualType == Type.bool && (intValue.isZero || intValue.isOne))
return this.module.createI32(intValue.isZero ? 0 : 1);
if (contextualType == Type.f64)
return this.module.createF64(intValue.toF64());
if (contextualType == Type.f32)
return this.module.createF32(<f32>intValue.toF64());
if (contextualType.is(TypeFlags.LONG | TypeFlags.INTEGER))
return this.module.createI64(intValue.lo, intValue.hi);
if (!intValue.fitsInI32) {
this.currentType = contextualType.is(TypeFlags.SIGNED) ? Type.i64 : Type.u64;
return this.module.createI64(intValue.lo, intValue.hi);
if (implicitNegate)
intValue = i64_sub(i64_new(0), intValue);
switch (contextualType.kind) {
// compile to contextualType if matching
case TypeKind.I8:
if (i64_is_i8(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.I16:
if (i64_is_i16(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.I32:
if (i64_is_i32(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.U8:
if (i64_is_u8(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.U16:
if (i64_is_u16(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.U32:
if (i64_is_u32(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.BOOL:
if (i64_is_bool(intValue))
return this.module.createI32(i64_low(intValue));
break;
case TypeKind.ISIZE:
if (!this.options.isWasm64) {
if (i64_is_u32(intValue))
return this.module.createI32(i64_low(intValue));
break;
}
return this.module.createI64(i64_low(intValue), i64_high(intValue));
case TypeKind.USIZE:
if (!this.options.isWasm64) {
if (i64_is_u32(intValue))
return this.module.createI32(i64_low(intValue));
break;
}
return this.module.createI64(i64_low(intValue), i64_high(intValue));
case TypeKind.I64:
case TypeKind.U64:
return this.module.createI64(i64_low(intValue), i64_high(intValue));
case TypeKind.F32:
if (i64_is_f32(intValue))
return this.module.createF32(i64_to_f32(intValue));
break;
case TypeKind.F64:
if (i64_is_f64(intValue))
return this.module.createF64(i64_to_f64(intValue));
break;
case TypeKind.VOID:
break;
default:
assert(false);
break;
}
if (contextualType.is(TypeFlags.SMALL | TypeFlags.INTEGER)) {
var shift = contextualType.computeSmallIntegerShift(Type.i32);
var mask = contextualType.computeSmallIntegerMask(Type.i32);
return this.module.createI32(contextualType.is(TypeFlags.SIGNED) ? intValue.lo << shift >> shift : intValue.lo & mask);
}
if (contextualType == Type.void && !intValue.fitsInI32) {
// otherwise compile to best fitting native type
if (i64_is_i32(intValue)) {
this.currentType = Type.i32;
return this.module.createI32(i64_low(intValue));
} else {
this.currentType = Type.i64;
return this.module.createI64(intValue.lo, intValue.hi);
return this.module.createI64(i64_low(intValue), i64_high(intValue));
}
this.currentType = contextualType.is(TypeFlags.SIGNED) ? Type.i32 : Type.u32;
return this.module.createI32(intValue.toI32());
case LiteralKind.STRING:
assert(!implicitNegate);
return this.compileStaticString((<StringLiteralExpression>expression).value);
// case LiteralKind.OBJECT:
@ -2955,14 +3018,15 @@ export class Compiler extends DiagnosticEmitter {
stringBuffer[4 + i * 2] = stringValue.charCodeAt(i) & 0xff;
stringBuffer[5 + i * 2] = (stringValue.charCodeAt(i) >>> 8) & 0xff;
}
stringSegment = this.addMemorySegment(stringBuffer);
stringSegment = this.addMemorySegment(stringBuffer, this.options.usizeType.byteSize);
this.stringSegments.set(stringValue, stringSegment);
}
var stringOffset = stringSegment.offset;
this.currentType = this.options.usizeType;
return this.options.isWasm64
? this.module.createI64(stringOffset.lo, stringOffset.hi)
: this.module.createI32(stringOffset.lo);
if (this.options.isWasm64)
return this.module.createI64(i64_low(stringOffset), i64_high(stringOffset));
assert(i64_is_i32(stringOffset));
return this.module.createI32(i64_low(stringOffset));
}
compileStaticArray(elementType: Type, expressions: (Expression | null)[]): ExpressionRef {
@ -3009,7 +3073,7 @@ export class Compiler extends DiagnosticEmitter {
break;
case NativeType.I64:
changetype<I64[]>(values)[i] = new I64(_BinaryenConstGetValueI64Low(expr), _BinaryenConstGetValueI64High(expr));
changetype<I64[]>(values)[i] = i64_new(_BinaryenConstGetValueI64Low(expr), _BinaryenConstGetValueI64High(expr));
break;
case NativeType.F32:
@ -3325,41 +3389,49 @@ export class Compiler extends DiagnosticEmitter {
break;
case Token.MINUS:
expr = this.compileExpression(expression.operand, contextualType == Type.void ? Type.i32 : contextualType, ConversionKind.NONE, false);
if (expression.operand.kind == NodeKind.LITERAL && (
(<LiteralExpression>expression.operand).literalKind == LiteralKind.INTEGER ||
(<LiteralExpression>expression.operand).literalKind == LiteralKind.FLOAT
)) {
// implicitly negate integer and float literals. also enables proper checking of literal ranges.
expr = this.compileLiteralExpression(<LiteralExpression>expression.operand, contextualType, true);
this.addDebugLocation(expr, expression.range); // compileExpression normally does this
} else {
expr = this.compileExpression(expression.operand, contextualType == Type.void ? Type.i32 : contextualType, ConversionKind.NONE, false);
switch (this.currentType.kind) {
switch (this.currentType.kind) {
case TypeKind.I8:
case TypeKind.I16:
case TypeKind.U8:
case TypeKind.U16:
case TypeKind.BOOL:
possiblyOverflows = true; // or if operand already did
default:
expr = this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), expr);
break;
case TypeKind.I8:
case TypeKind.I16:
case TypeKind.U8:
case TypeKind.U16:
case TypeKind.BOOL:
possiblyOverflows = true; // or if operand already did
default:
expr = this.module.createBinary(BinaryOp.SubI32, this.module.createI32(0), expr);
break;
case TypeKind.USIZE:
if (this.currentType.isReference) {
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return this.module.createUnreachable();
}
case TypeKind.ISIZE:
expr = this.module.createBinary(this.options.target == Target.WASM64 ? BinaryOp.SubI64 : BinaryOp.SubI32, this.currentType.toNativeZero(this.module), expr);
break;
case TypeKind.USIZE:
if (this.currentType.isReference) {
this.error(DiagnosticCode.Operation_not_supported, expression.range);
return this.module.createUnreachable();
}
case TypeKind.ISIZE:
expr = this.module.createBinary(this.options.target == Target.WASM64 ? BinaryOp.SubI64 : BinaryOp.SubI32, this.currentType.toNativeZero(this.module), expr);
break;
case TypeKind.I64:
case TypeKind.U64:
expr = this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0), expr);
break;
case TypeKind.I64:
case TypeKind.U64:
expr = this.module.createBinary(BinaryOp.SubI64, this.module.createI64(0), expr);
break;
case TypeKind.F32:
expr = this.module.createUnary(UnaryOp.NegF32, expr);
break;
case TypeKind.F32:
expr = this.module.createUnary(UnaryOp.NegF32, expr);
break;
case TypeKind.F64:
expr = this.module.createUnary(UnaryOp.NegF64, expr);
break;
case TypeKind.F64:
expr = this.module.createUnary(UnaryOp.NegF64, expr);
break;
}
}
break;

@ -11,10 +11,6 @@ import {
readString
} from "./module";
import {
I64
} from "./util/i64";
// TODO :-)
export class Decompiler {
@ -28,8 +24,6 @@ export class Decompiler {
text: string[] = [];
functionId: i32 = 0;
private tempI64: I64 = new I64();
constructor() { }
/** Decompiles a module to an AST that can then be serialized. */
@ -183,9 +177,14 @@ export class Decompiler {
return;
case NativeType.I64:
this.tempI64.lo = _BinaryenConstGetValueI64Low(expr);
this.tempI64.hi = _BinaryenConstGetValueI64High(expr);
this.push(this.tempI64.toString());
this.push(
i64_to_string(
i64_new(
_BinaryenConstGetValueI64Low(expr),
_BinaryenConstGetValueI64High(expr)
)
)
);
return;
case NativeType.F32:

6
src/extra/tsconfig.json Normal file

@ -0,0 +1,6 @@
{
"extends": "../../std/portable.json",
"include": [
"./**/*.ts"
]
}

@ -1,54 +0,0 @@
require("../../std/portable");
// Copy Binaryen exports to global scope
var globalScope = typeof window !== "undefined" && window || typeof global !== "undefined" && global || self;
var binaryen = globalScope["Binaryen"]; // allow overriding for testing purposes
if (!binaryen) {
try {
binaryen = require("binaryen");
} catch (e) {
binaryen = globalScope["Binaryen"];
}
}
for (var key in binaryen)
if (/^_(?:Binaryen|Relooper)/.test(key))
globalScope[key] = binaryen[key];
// Use Binaryen's heap instead of std heap
globalScope["allocate_memory"] = function allocate_memory(size) {
if (!size) return 0; // should be safe in our case
return binaryen._malloc(size);
};
globalScope["free_memory"] = function free_memory(ptr) {
if (ptr) binaryen._free(ptr);
};
globalScope["move_memory"] = function move_memory(dest, src, n) {
return binaryen._memmove(dest, src, n);
};
globalScope["store"] = function store(ptr, val) {
binaryen.HEAPU8[ptr] = val;
};
globalScope["load"] = function load(ptr) {
return binaryen.HEAPU8[ptr];
};
// Implement module stubs
var Module = require("../module").Module;
Module.prototype.toText = function toText() {
var previousPrint = binaryen.print;
var ret = "";
binaryen.print = function print(x) { ret += x + "\n" };
this.print();
binaryen.print = previousPrint;
return ret;
};
Module.prototype.toAsmjs = function toAsmjs() {
var previousPrint = binaryen.print;
var ret = "";
binaryen.print = function print(x) { ret += x + "\n" };
this.printAsmjs();
binaryen.print = previousPrint;
return ret;
};

36
src/glue/js/i64.d.ts vendored Normal file

@ -0,0 +1,36 @@
declare type I64 = Long;
declare function i64_new(lo: i32, hi?: i32): I64;
declare function i64_low(value: I64): i32;
declare function i64_high(value: I64): i32;
declare function i64_add(left: I64, right: I64): I64;
declare function i64_sub(left: I64, right: I64): I64;
declare function i64_mul(left: I64, right: I64): I64;
declare function i64_div(left: I64, right: I64): I64;
declare function i64_div_u(left: I64, right: I64): I64;
declare function i64_rem(left: I64, right: I64): I64;
declare function i64_rem_u(left: I64, right: I64): I64;
declare function i64_and(left: I64, right: I64): I64;
declare function i64_or(left: I64, right: I64): I64;
declare function i64_xor(left: I64, right: I64): I64;
declare function i64_shl(left: I64, right: I64): I64;
declare function i64_shr(left: I64, right: I64): I64;
declare function i64_shr_u(left: I64, right: I64): I64;
declare function i64_not(value: I64): I64;
declare function i64_align(value: I64, alignment: i32): I64;
declare function i64_is_i8(value: I64): bool;
declare function i64_is_i16(value: I64): bool;
declare function i64_is_i32(value: I64): bool;
declare function i64_is_u8(value: I64): bool;
declare function i64_is_u16(value: I64): bool;
declare function i64_is_u32(value: I64): bool;
declare function i64_is_bool(value: I64): bool;
declare function i64_is_f32(value: I64): bool;
declare function i64_is_f64(value: I64): bool;
declare function i64_to_f32(value: I64): f64;
declare function i64_to_f64(value: I64): f64;
declare function i64_to_string(value: I64, unsigned?: bool): string;

193
src/glue/js/index.ts Normal file

@ -0,0 +1,193 @@
import "../../../std/portable";
// Copy Binaryen exports to global scope
declare const global: any;
declare function require(name: string): any;
const binaryen: any = global.Binaryen || require("binaryen");
for (let key in binaryen)
if (key.startsWith("_Binaryen") || key.startsWith("_Relooper"))
global[key] = (<any>binaryen)[key];
// Use Binaryen's heap instead of std heap
global.allocate_memory = function(size: number): number {
if (!size) return 0; // should be safe in our case
return (<any>binaryen)._malloc(size);
};
global.free_memory = function(ptr: number): void {
if (ptr) (<any>binaryen)._free(ptr);
};
global.move_memory = function(dest: number, src: number, n: number): number {
return (<any>binaryen)._memmove(dest, src, n);
};
global.store = function(ptr: number, val: number): void {
(<any>binaryen).HEAPU8[ptr] = val;
};
global.load = function(ptr: number): number {
return (<any>binaryen).HEAPU8[ptr];
};
// Implement module stubs
import { Module } from "../../module";
Module.prototype.toText = function toText() {
var previousPrint = binaryen.print;
var ret = "";
binaryen.print = (x: string) => { ret += x + "\n" };
this.print();
binaryen.print = previousPrint;
return ret;
};
Module.prototype.toAsmjs = function toAsmjs() {
var previousPrint = binaryen.print;
var ret = "";
binaryen.print = (x: string) => { ret += x + "\n" };
this.printAsmjs();
binaryen.print = previousPrint;
return ret;
};
// Implement I64 using long.js
import * as Long from "long";
/// <reference path="./i64.d.ts" />
global.i64_new = function(lo: number, hi: number = 0): I64 {
return Long.fromBits(lo, hi);
};
global.i64_low = function(value: I64): i32 {
return value.low;
};
global.i64_high = function(value: I64): i32 {
return value.high;
};
global.i64_add = function(left: I64, right: I64): I64 {
return left.add(right);
};
global.i64_sub = function(left: I64, right: I64): I64 {
return left.sub(right);
};
global.i64_mul = function(left: I64, right: I64): I64 {
return left.mul(right);
};
global.i64_div = function(left: I64, right: I64): I64 {
return left.div(right);
};
global.i64_div_u = function(left: I64, right: I64): I64 {
return left.toUnsigned().div(right.toUnsigned()).toSigned();
};
global.i64_rem = function(left: I64, right: I64): I64 {
return left.mod(right);
};
global.i64_rem_u = function(left: I64, right: I64): I64 {
return left.toUnsigned().mod(right.toUnsigned()).toSigned();
};
global.i64_and = function(left: I64, right: I64): I64 {
return left.and(right);
};
global.i64_or = function(left: I64, right: I64): I64 {
return left.or(right);
};
global.i64_xor = function(left: I64, right: I64): I64 {
return left.xor(right);
};
global.i64_shl = function(left: I64, right: I64): I64 {
return left.shl(right);
};
global.i64_shr = function(left: I64, right: I64): I64 {
return left.shr(right);
};
global.i64_shr_u = function(left: I64, right: I64): I64 {
return left.shru(right);
};
global.i64_not = function(value: I64): I64 {
return value.not();
};
global.i64_align = function(value: I64, alignment: i32): I64 {
assert(alignment && (alignment & (alignment - 1)) == 0);
var mask = Long.fromInt(alignment - 1);
return value.add(mask).and(mask.not());
};
global.i64_is_i8 = function(value: I64): bool {
return value.high === 0 && (value.low >= 0 && value.low <= i8.MAX_VALUE)
|| value.high === -1 && (value.low >= i8.MIN_VALUE && value.low < 0);
};
global.i64_is_i16 = function(value: I64): bool {
return value.high === 0 && (value.low >= 0 && value.low <= i16.MAX_VALUE)
|| value.high === -1 && (value.low >= i16.MIN_VALUE && value.low < 0);
};
global.i64_is_i32 = function(value: I64): bool {
return (value.high === 0 && value.low >= 0) || (value.high === -1 && value.low < 0);
};
global.i64_is_u8 = function(value: I64): bool {
return value.high === 0 && value.low >= 0 && value.low <= u8.MAX_VALUE;
};
global.i64_is_u16 = function(value: I64): bool {
return value.high === 0 && value.low >= 0 && value.low <= u16.MAX_VALUE;
};
global.i64_is_u32 = function(value: I64): bool {
return value.high === 0;
};
global.i64_is_bool = function(value: I64): bool {
return value.high === 0 && (value.low === 0 || value.low === 1);
};
const minSafeF32 = Long.fromNumber(f32.MIN_SAFE_INTEGER);
const maxSafeF32 = Long.fromNumber(f32.MAX_SAFE_INTEGER);
global.i64_is_f32 = function(value: I64): bool {
return value.gte(minSafeF32) && value.lte(maxSafeF32);
};
const minSafeF64 = Long.fromNumber(f64.MIN_SAFE_INTEGER);
const maxSafeF64 = Long.fromNumber(f64.MAX_SAFE_INTEGER);
global.i64_is_f64 = function(value: I64): bool {
return value.gte(minSafeF64) && value.lte(maxSafeF64);
};
global.i64_to_f32 = function(value: I64): f64 {
return global.Math.fround(value.toNumber());
};
global.i64_to_f64 = function(value: I64): f64 {
return value.toNumber();
};
global.i64_to_string = function(value: I64, unsigned: bool = false): string {
return (unsigned ? value.toUnsigned() : value).toString(10);
};

164
src/glue/wasm/index.ts Normal file

@ -0,0 +1,164 @@
type I64 = i64;
@global
function i64_new(lo: i32, hi: i32 = 0): I64 {
return lo | (hi << 32);
}
@global
function i64_low(value: I64): i32 {
return <i32>value;
}
@global
function i64_high(value: I64): i32 {
return <i32>(value >>> 32);
}
@global
function i64_add(left: I64, right: I64): I64 {
return left + right;
}
@global
function i64_sub(left: I64, right: I64): I64 {
return left - right;
}
@global
function i64_mul(left: I64, right: I64): I64 {
return left * right;
}
@global
function i64_div(left: I64, right: I64): I64 {
return left / right;
}
@global
function i64_div_u(left: I64, right: I64): I64 {
return <u64>left / <u64>right;
}
@global
function i64_rem(left: I64, right: I64): I64 {
return left % right;
}
@global
function i64_rem_u(left: I64, right: I64): I64 {
return <u64>left % <u64>right;
}
@global
function i64_and(left: I64, right: I64): I64 {
return left & right;
}
@global
function i64_or(left: I64, right: I64): I64 {
return left | right;
}
@global
function i64_xor(left: I64, right: I64): I64 {
return left ^ right;
}
@global
function i64_shl(left: I64, right: I64): I64 {
return left << right;
}
@global
function i64_shr(left: I64, right: I64): I64 {
return left >> right;
}
@global
function i64_shr_u(left: I64, right: I64): I64 {
return left >>> right;
}
@global
function i64_not(value: I64): I64 {
return ~value;
}
@global
function i64_align(value: I64, alignment: i64): I64 {
var mask: i64 = alignment - 1;
assert(alignment && (alignment & mask) == 0);
return (value + mask) & ~mask;
}
@global
function i64_is_i8(value: I64): bool {
return value >= i8.MIN_VALUE && value <= i8.MAX_VALUE;
}
@global
function i64_is_i16(value: I64): bool {
return value >= i16.MIN_VALUE && value <= i16.MAX_VALUE;
}
@global
function i64_is_i32(value: I64): bool {
return value >= i32.MIN_VALUE && value <= i32.MAX_VALUE;
}
@global
function i64_is_u8(value: I64): bool {
return value >= 0 && value <= u8.MAX_VALUE;
}
@global
function i64_is_u16(value: I64): bool {
return value >= 0 && value <= u16.MAX_VALUE;
}
@global
function i64_is_u32(value: I64): bool {
return value >= 0 && value <= u32.MAX_VALUE;
}
@global
function i64_is_bool(value: I64): bool {
return value === 0 || value === 1;
}
@global
function i64_is_f32(value: I64): bool {
return value >= f32.MIN_SAFE_INTEGER && value <= f32.MAX_SAFE_INTEGER;
}
@global
function i64_is_f64(value: I64): bool {
return value >= f64.MIN_SAFE_INTEGER && value <= f64.MAX_SAFE_INTEGER;
}
@global
function i64_to_f32(value: I64): f32 {
return <f32>value;
}
@global
function i64_to_f64(value: I64): f64 {
return <f64>value;
}
import { CharCode } from "../../util/charcode";
@global
function i64_to_string(value: I64): string {
var chars = new Array<u16>();
if (value < 0) {
chars.push(CharCode.MINUS);
value = -value;
}
do {
chars.push(CharCode._0 + (value % 10));
value /= 10;
} while (value);
return String.fromCharCodes(chars);
}

@ -0,0 +1,6 @@
{
"extends": "../../../std/assembly.json",
"include": [
"./**/*.ts"
]
}

@ -2,10 +2,6 @@ import {
Target
} from "./compiler";
import {
U64
} from "./util/i64";
export type ModuleRef = usize;
export type FunctionTypeRef = usize;
export type FunctionRef = usize;
@ -225,9 +221,9 @@ export enum AtomicRMWOp {
export class MemorySegment {
buffer: Uint8Array;
offset: U64;
offset: I64;
static create(buffer: Uint8Array, offset: U64) {
static create(buffer: Uint8Array, offset: I64) {
var segment = new MemorySegment();
segment.buffer = buffer;
segment.offset = offset;
@ -641,8 +637,8 @@ export class Module {
var offset = segments[i].offset;
segs[i] = allocU8Array(buffer);
offs[i] = target == Target.WASM64
? this.createI64(offset.lo, offset.hi)
: this.createI32(offset.toI32());
? this.createI64(i64_low(offset), i64_high(offset))
: this.createI32(i64_low(offset));
sizs[i] = buffer.length;
}
var cArr1 = allocI32Array(segs);
@ -747,8 +743,11 @@ export class Module {
}
toText(): string {
// FIXME: target specific / JS glue overrides this
throw new Error("not implemented");
throw new Error("not implemented"); // JS glue overrides this
}
toAsmjs(): string {
throw new Error("not implemented"); // JS glue overrides this
}
dispose(): void {

@ -23,10 +23,6 @@ import {
DiagnosticEmitter
} from "./diagnostics";
import {
I64
} from "./util/i64";
import {
normalize as normalizePath
} from "./util/path";

@ -13,10 +13,6 @@ import {
typesToString
} from "./types";
import {
I64
} from "./util/i64";
import {
ModifierKind,
Node,
@ -1446,6 +1442,12 @@ export class EnumValue extends Element {
}
}
export const enum ConstantValueKind {
NONE,
INTEGER,
FLOAT
}
export class VariableLikeElement extends Element {
// kind varies
@ -1454,18 +1456,22 @@ export class VariableLikeElement extends Element {
declaration: VariableLikeDeclarationStatement;
/** Variable type. Is {@link Type.void} for type-inferred {@link Global}s before compilation. */
type: Type;
/** Constant value kind. */
constantValueKind: ConstantValueKind = ConstantValueKind.NONE;
/** Constant integer value, if applicable. */
constantIntegerValue: I64 | null = null;
constantIntegerValue: I64;
/** Constant float value, if applicable. */
constantFloatValue: f64 = 0;
constantFloatValue: f64;
withConstantIntegerValue(lo: i32, hi: i32): this {
this.constantIntegerValue = new I64(lo, hi);
this.constantValueKind = ConstantValueKind.INTEGER;
this.constantIntegerValue = i64_new(lo, hi);
this.set(ElementFlags.CONSTANT | ElementFlags.INLINED);
return this;
}
withConstantFloatValue(value: f64): this {
this.constantValueKind = ConstantValueKind.FLOAT;
this.constantFloatValue = value;
this.set(ElementFlags.CONSTANT | ElementFlags.INLINED);
return this;

@ -39,10 +39,6 @@ import {
isKeywordCharacter
} from "./util/charcode";
import {
I64
} from "./util/i64";
/** Named token types. */
export enum Token {
@ -993,21 +989,19 @@ export class Tokenizer extends DiagnosticEmitter {
readHexInteger(): I64 {
var text = this.source.text;
var start = this.pos;
var value = new I64(0, 0);
var value = i64_new(0, 0);
var i64_16 = i64_new(16, 0);
while (this.pos < this.end) {
var c = text.charCodeAt(this.pos);
if (c >= CharCode._0 && c <= CharCode._9) {
// value = value * 16 + c - CharCode._0;
value.mul32(16);
value.add32(c - CharCode._0);
} else if (c >= CharCode.A && c <= CharCode.F) {
value = i64_add(i64_mul(value, i64_16), i64_new(c - CharCode._0, 0));
} else if (c >= CharCode.A && c <= CharCode.F) {
// value = value * 16 + 10 + c - CharCode.A;
value.mul32(16);
value.add32(10 + c - CharCode.A);
value = i64_add(i64_mul(value, i64_16), i64_new(10 + c - CharCode.A, 0));
} else if (c >= CharCode.a && c <= CharCode.f) {
// value = value * 16 + 10 + c - CharCode.a;
value.mul32(16);
value.add32(10 + c - CharCode.a);
value = i64_add(i64_mul(value, i64_16), i64_new(10 + c - CharCode.a, 0));
} else
break;
++this.pos;
@ -1020,13 +1014,13 @@ export class Tokenizer extends DiagnosticEmitter {
readDecimalInteger(): I64 {
var text = this.source.text;
var start = this.pos;
var value = new I64(0, 0);
var value = i64_new(0, 0);
var i64_10 = i64_new(10, 0);
while (this.pos < this.end) {
var c = text.charCodeAt(this.pos);
if (c >= CharCode._0 && c <= CharCode._9) {
// value = value * 10 + c - CharCode._0;
value.mul32(10);
value.add32(c - CharCode._0);
value = i64_add(i64_mul(value, i64_10), i64_new(c - CharCode._0, 0));
} else
break;
++this.pos;
@ -1039,13 +1033,13 @@ export class Tokenizer extends DiagnosticEmitter {
readOctalInteger(): I64 {
var text = this.source.text;
var start = this.pos;
var value = new I64(0, 0);
var value = i64_new(0, 0);
var i64_8 = i64_new(8, 0);
while (this.pos < this.end) {
var c = text.charCodeAt(this.pos);
if (c >= CharCode._0 && c <= CharCode._7) {
// value = value * 8 + c - CharCode._0;
value.mul32(8);
value.add32(c - CharCode._0);
value = i64_add(i64_mul(value, i64_8), i64_new(c - CharCode._0, 0));
} else
break;
++this.pos;
@ -1058,18 +1052,18 @@ export class Tokenizer extends DiagnosticEmitter {
readBinaryInteger(): I64 {
var text = this.source.text;
var start = this.pos;
var value = new I64();
var value = i64_new(0, 0);
var i64_2 = i64_new(2, 0);
var i64_1 = i64_new(1, 0);
while (this.pos < this.end) {
var c = text.charCodeAt(this.pos);
if (c == CharCode._0) {
// value = value * 2;
value.mul32(2);
value = i64_mul(value, i64_2);
} else if (c == CharCode._1) {
// value = value * 2 + 1;
value.mul32(2);
value.add32(1);
}
else
value = i64_add(i64_mul(value, i64_2), i64_1);
} else
break;
++this.pos;
}
@ -1129,14 +1123,15 @@ export class Tokenizer extends DiagnosticEmitter {
private readExtendedUnicodeEscape(): string {
var start = this.pos;
var value = this.readHexInteger();
var value32 = i64_low(value);
var invalid = false;
if (value.gt32(0x10FFFF)) {
assert(!i64_high(value));
if (value32 > 0x10FFFF) {
this.error(DiagnosticCode.An_extended_Unicode_escape_value_must_be_between_0x0_and_0x10FFFF_inclusive, this.range(start, this.pos));
invalid = true;
}
var value32 = value.toI32();
var text = this.source.text;
if (this.pos >= this.end) {
this.error(DiagnosticCode.Unexpected_end_of_text, this.range(start, this.end));

@ -8,6 +8,7 @@
"./**/*.ts"
],
"exclude": [
"./extra/**"
"./extra/**",
"./glue/wasm/**"
]
}

@ -1,537 +0,0 @@
/*
To remain compatible with TSC / compiling to JS, we have to emulate I64s in a
portable way. The following is based on long.js with the main difference being
that instances are mutable and operations affect 'this'. In our scenario,
that's useful because it's mostly used for constant evaluation and we are
exclusively interested in the result (saves a heap of allocations).
*/
// TODO: div/mod
// another option is to use a wasm-based polyfill, see examples/i64-polyfill.
const I64_MIN_LO: i32 = 0;
const I64_MIN_HI: i32 = 0x80000000 | 0;
export class I64 {
lo: i32;
hi: i32;
static fromI32(n: i32): I64 {
return new I64(n, n < 0 ? -1 : 0);
}
constructor(lo: i32 = 0, hi: i32 = 0) {
this.lo = lo;
this.hi = hi;
}
get isZero(): bool {
return this.lo == 0 && this.hi == 0;
}
get isOne(): bool {
return this.lo == 1 && this.hi == 0;
}
get isPositive(): bool {
return this.hi >= 0;
}
get isNegative(): bool {
return this.hi < 0;
}
get isOdd(): bool {
return (this.lo & 1) == 1;
}
get isEven(): bool {
return (this.lo & 1) == 0;
}
get fitsInI32(): bool {
return this.hi == 0 || (this.hi == -1 && this.lo < 0);
}
toI32(): i32 {
return this.lo;
}
toF64(): f64 {
return <f64>this.hi * 0x100000000 + <f64>(this.lo >>> 0);
}
eq(other: I64): bool {
return this.eq32(other.lo, other.hi);
}
eq32(lo: i32, hi: i32 = 0): bool {
return this.lo == lo && this.hi == hi;
}
ne(other: I64): bool {
return this.ne32(other.lo, other.hi);
}
ne32(lo: i32, hi: i32 = 0): bool {
return this.lo != lo || this.hi != hi;
}
neg(): void {
this.lo = ~this.lo;
this.hi = ~this.hi;
this.add32(1, 0);
}
add(other: I64): void {
this.add32(other.lo, other.hi);
}
add32(lo: i32, hi: i32 = 0): void {
i64_add_internal(this.lo, this.hi, lo, hi);
this.lo = i64_lo;
this.hi = i64_hi;
}
sub(other: I64): void {
this.sub32(other.lo, other.hi);
}
sub32(lo: i32, hi: i32 = 0): void {
i64_add_internal(~lo, ~hi, 1, 0);
this.add32(i64_lo, i64_hi);
}
comp(other: I64): i32 {
return this.comp32(other.lo, other.hi);
}
comp32(lo: i32, hi: i32 = 0): i32 {
if (this.lo == lo && this.hi == hi)
return 0;
if (this.hi < 0 && hi >= 0)
return -1;
if (this.hi >= 0 && hi < 0)
return 1;
i64_add_internal(~lo, ~hi, 1, 0);
i64_add_internal(this.lo, this.hi, i64_lo, i64_hi);
return i64_hi < 0 ? -1 : 1;
}
lt(other: I64): bool {
return this.lt32(other.lo, other.hi);
}
lt32(lo: i32, hi: i32 = 0): bool {
return this.comp32(lo, hi) < 0;
}
lte(other: I64): bool {
return this.lte32(other.lo, other.hi);
}
lte32(lo: i32, hi: i32 = 0): bool {
return this.comp32(lo, hi) <= 0;
}
gt(other: I64): bool {
return this.gt32(other.lo, other.hi);
}
gt32(lo: i32, hi: i32 = 0): bool {
return this.comp32(lo, hi) > 0;
}
gte(other: I64): bool {
return this.gte32(other.lo, other.hi);
}
gte32(lo: i32, hi: i32 = 0): bool {
return this.comp32(lo, hi) >= 0;
}
mul(other: I64): void {
this.mul32(other.lo, other.hi);
}
mul32(lo: i32, hi: i32 = 0): void {
if (this.lo == 0 && this.hi == 0)
return;
if (lo == 0 && hi == 0) {
this.lo = 0;
this.hi = 0;
return;
}
// this == MIN
if (this.lo == I64_MIN_LO && this.hi == I64_MIN_HI) {
this.lo = 0; // == MIN_LO
this.hi = lo & 1 ? I64_MIN_HI : 0; // other.isOdd ? this = MIN : this = ZERO
return;
}
// other == MIN
if (lo == I64_MIN_LO && hi == I64_MIN_HI) {
this.hi = this.lo & 1 ? I64_MIN_HI : 0; // this.isOdd ? this = MIN : this = ZERO
this.lo = 0; // == MIN_LO
return;
}
if (this.hi < 0) {
this.neg();
// both negative: negate both and multiply
if (hi < 0) {
i64_add_internal(~lo, ~hi, 1, 0);
i64_mul_internal(this.lo, this.hi, i64_lo, i64_hi);
this.lo = i64_lo;
this.hi = i64_hi;
// this negative: negate this, multiply and negate result
} else {
i64_mul_internal(this.lo, this.hi, lo, hi);
this.lo = i64_lo;
this.hi = i64_hi;
this.neg();
}
return;
// other negative: negate other, multiply and negate result
} else if (hi < 0) {
i64_add_internal(~lo, ~hi, 1, 0);
i64_mul_internal(this.lo, this.hi, i64_lo, i64_hi);
this.lo = i64_lo;
this.hi = i64_hi;
this.neg();
return;
}
// both positive
i64_mul_internal(this.lo, this.hi, lo, hi);
this.lo = i64_lo;
this.hi = i64_hi;
}
div(other: I64): void {
this.div32(other.lo, other.hi);
}
div32(lo: i32, hi: i32 = 0): void {
// other == 0
if (lo == 0 && hi == 0)
throw new Error("division by zero");
// this == 0
if (this.lo == 0 && this.hi == 0)
return;
// this == MIN
if (this.lo == I64_MIN_LO && this.hi == I64_MIN_HI) {
// other == 1 or -1
if (lo == 1 && hi == 0 || lo == -1 && hi == -1) // -MIN == MIN
return;
// both == MIN
if (lo == I64_MIN_LO && hi == I64_MIN_HI) {
this.lo = 1;
this.hi = 0;
return;
}
// |other| >= 2, so |this/other| < |MIN_VALUE|
var tempLo = this.lo;
var tempHi = this.hi;
this.shr32(1, 0);
this.div32(lo, hi);
this.shl32(1, 0);
if (this.lo == 0 && this.hi == 0) {
if (hi < 0) {
this.lo = 1;
this.hi = 0;
} else {
this.lo = -1;
this.hi = -1;
}
return;
}
i64_mul_internal(lo, hi, this.lo, this.hi);
this.lo = tempLo;
this.hi = tempHi;
tempLo = i64_lo;
tempHi = i64_hi;
this.div32(lo, hi);
this.sub32(i64_lo, i64_hi);
i64_add_internal(tempLo, tempHi, this.lo, this.hi);
this.lo = i64_lo;
this.hi = i64_hi;
return;
}
if (this.hi < 0) {
this.neg();
// both negative: negate both and divide
if (hi < 0) {
i64_add_internal(~lo, ~hi, 1, 0);
i64_div_internal(this.lo, this.hi, i64_lo, i64_hi);
this.lo = i64_lo;
this.hi = i64_hi;
// this negative: negate this, divide and negate result
} else {
i64_div_internal(this.lo, this.hi, lo, hi);
this.lo = i64_lo;
this.hi = i64_hi;
this.neg();
}
return;
// other negative: negate other, divide and negate result
} else if (hi < 0) {
i64_add_internal(~lo, ~hi, 1, 0);
i64_div_internal(this.lo, this.hi, i64_lo, i64_hi);
this.lo = i64_lo;
this.hi = i64_hi;
this.neg();
return;
}
// both positive
i64_div_internal(this.lo, this.hi, lo, hi);
this.lo = i64_lo;
this.hi = i64_hi;
}
mod(other: I64): void {
this.mod32(other.lo, other.hi);
}
mod32(lo: i32, hi: i32 = 0): void {
var thisLo = this.lo;
var thisHi = this.hi;
this.div32(lo, hi);
this.mul32(lo, hi);
var resLo = this.lo;
var resHi = this.hi;
this.lo = thisLo;
this.hi = thisHi;
this.sub32(resLo, resHi);
}
not(): void {
this.lo = ~this.lo;
this.hi = ~this.hi;
}
and(other: I64): void {
this.and32(other.lo, other.hi);
}
and32(lo: i32, hi: i32 = 0): void {
this.lo &= lo;
this.hi &= hi;
}
or(other: I64): void {
this.or32(other.lo, other.hi);
}
or32(lo: i32, hi: i32 = 0): void {
this.lo |= lo;
this.hi |= hi;
}
xor(other: I64): void {
this.xor32(other.lo, other.hi);
}
xor32(lo: i32, hi: i32 = 0): void {
this.lo ^= lo;
this.hi ^= hi;
}
shl(other: I64): void {
this.shl32(other.lo, other.hi);
}
shl32(lo: i32, hi: i32 = 0): void {
if ((lo &= 63) == 0)
return;
if (lo < 32) {
this.hi = (this.hi << lo) | (this.lo >>> (32 - lo));
this.lo = this.lo << lo;
} else {
this.hi = this.lo << (lo - 32);
this.lo = 0;
}
}
shr(other: I64): void {
this.shr32(other.lo, other.hi);
}
shr32(lo: i32, hi: i32 = 0): void {
if ((lo &= 63) == 0)
return;
if (lo < 32) {
this.lo = (this.lo >>> lo) | (this.hi << (32 - lo));
this.hi = this.hi >> lo;
} else {
this.lo = this.hi >> (lo - 32);
this.hi = this.hi >= 0 ? 0 : -1;
}
}
shru(other: I64): void {
this.shru32(other.lo, other.hi);
}
shru32(lo: i32, hi: i32 = 0): void {
if ((lo &= 63) == 0)
return;
if (lo < 32) {
this.lo = (this.lo >>> lo) | (this.hi << (32 - lo));
this.hi = (this.hi >>> lo) | 0;
} else if (lo == 32) {
this.lo = this.hi;
this.hi = 0;
} else {
this.lo = (this.hi >>> (lo - 32)) | 0;
this.hi = 0;
}
}
clone(): I64 {
return new I64(this.lo, this.hi);
}
toString(): string {
var negative = false;
if (this.hi < 0) {
i64_add_internal(~this.lo, ~this.hi, 1, 0);
negative = true;
} else {
i64_lo = this.lo;
i64_hi = this.hi;
}
if (i64_hi) {
var lo = (i64_lo as u32 >>> 0).toString(16);
while (lo.length < 8)
lo = "0" + lo;
return (negative ? "-0x" : "0x") + (i64_hi as u32 >>> 0).toString(16) + lo;
}
return negative ? "-" + i64_lo.toString(10) : i64_lo.toString(10);
}
}
var i64_lo = 0;
var i64_hi = 0;
function i64_add_internal(lo: i32, hi: i32, otherLo: i32, otherHi: i32): void {
var a48 = hi >>> 16;
var a32 = hi & 0xFFFF;
var a16 = lo >>> 16;
var a00 = lo & 0xFFFF;
var b48 = otherHi >>> 16;
var b32 = otherHi & 0xFFFF;
var b16 = otherLo >>> 16;
var b00 = otherLo & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 + b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 + b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 + b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 + b48;
c48 &= 0xFFFF;
i64_lo = (c16 << 16) | c00;
i64_hi = (c48 << 16) | c32;
}
function i64_mul_internal(lo: i32, hi: i32, otherLo: i32, otherHi: i32): void {
var a48 = hi >>> 16;
var a32 = hi & 0xFFFF;
var a16 = lo >>> 16;
var a00 = lo & 0xFFFF;
var b48 = otherHi >>> 16;
var b32 = otherHi & 0xFFFF;
var b16 = otherLo >>> 16;
var b00 = otherLo & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 * b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 * b00;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c16 += a00 * b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 * b00;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a16 * b16;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a00 * b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
c48 &= 0xFFFF;
i64_lo = (c16 << 16) | c00;
i64_hi = (c48 << 16) | c32;
}
function i64_div_internal(lo: i32, hi: i32, otherLo: i32, otherHi: i32): void {
throw new Error("not implemented");
}
export class U64 extends I64 {
static fromI32(n: i32): U64 {
return new U64(n, 0);
}
get isPositive(): bool {
return true;
}
get isNegative(): bool {
return false;
}
get fitsInU32(): bool {
return this.hi == 0;
}
comp32(lo: i32, hi: i32): i32 {
// uses both a cast and a js-like shift for portability
return ((hi as u32 >>> 0) > (this.hi as u32 >>> 0)) || (hi == this.hi && (lo as u32 >>> 0) > (this.lo as u32 >>> 0)) ? -1 : 1;
}
neg(): void {
this.lo = ~this.lo;
this.hi = ~this.hi;
this.add32(1, 0);
}
clone(): U64 {
return new U64(this.lo, this.hi);
}
}