Initial ArrayBuffer implementation; Conditional allocation within constructors; Explicit constructor return values

This commit is contained in:
dcodeIO 2018-03-23 01:47:01 +01:00
parent 8cfc479cc0
commit 9cc0fcd611
18 changed files with 6285 additions and 176 deletions

2
dist/asc.js vendored

File diff suppressed because one or more lines are too long

2
dist/asc.js.map vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2462,44 +2462,46 @@ const allocateInternalName = "allocate_memory";
/** Compiles a memory allocation for an instance of the specified class. */
export function compileAllocate(
compiler: Compiler,
cls: Class,
classInstance: Class,
reportNode: Node
): ExpressionRef {
var program = compiler.program;
assert(cls.program == program);
assert(classInstance.program == program);
var module = compiler.module;
var options = compiler.options;
var prototype = program.elementsLookup.get(allocateInternalName);
if (!prototype) {
var allocatePrototype = program.elementsLookup.get(allocateInternalName);
if (!allocatePrototype) {
program.error(
DiagnosticCode.Cannot_find_name_0,
reportNode.range, allocateInternalName
);
return module.createUnreachable();
}
if (prototype.kind != ElementKind.FUNCTION_PROTOTYPE) {
if (allocatePrototype.kind != ElementKind.FUNCTION_PROTOTYPE) {
program.error(
DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures,
reportNode.range, prototype.internalName
reportNode.range, allocatePrototype.internalName
);
return module.createUnreachable();
}
var instance = (<FunctionPrototype>prototype).resolve(); // reports
if (!(instance && compiler.compileFunction(instance))) return module.createUnreachable();
var allocateInstance = (<FunctionPrototype>allocatePrototype).resolve(); // reports
if (!(allocateInstance && compiler.compileFunction(allocateInstance))) return module.createUnreachable();
compiler.currentType = cls.type;
compiler.currentType = classInstance.type;
return module.createCall(
instance.internalName, [
allocateInstance.internalName, [
options.isWasm64
? module.createI64(cls.currentMemoryOffset)
: module.createI32(cls.currentMemoryOffset)
? module.createI64(classInstance.currentMemoryOffset)
: module.createI32(classInstance.currentMemoryOffset)
],
options.nativeSizeType
);
}
const abortInternalName = "abort";
/** Compiles an abort wired to the conditionally imported 'abort' function. */
export function compileAbort(
compiler: Compiler,
@ -2512,7 +2514,7 @@ export function compileAbort(
var stringType = program.typesLookup.get("string"); // might be intended
if (!stringType) return module.createUnreachable();
var abortPrototype = program.elementsLookup.get("abort"); // might be intended
var abortPrototype = program.elementsLookup.get(abortInternalName); // might be intended
if (!abortPrototype || abortPrototype.kind != ElementKind.FUNCTION_PROTOTYPE) return module.createUnreachable();
var abortInstance = (<FunctionPrototype>abortPrototype).resolve(); // reports

View File

@ -840,7 +840,8 @@ export class Compiler extends DiagnosticEmitter {
var typeRef = this.ensureFunctionType(instance.signature);
var module = this.module;
if (body) {
let returnType = instance.signature.returnType;
let isConstructor = instance.is(CommonFlags.CONSTRUCTOR);
let returnType: Type = instance.signature.returnType;
// compile body
let previousFunction = this.currentFunction;
@ -848,15 +849,43 @@ export class Compiler extends DiagnosticEmitter {
let flow = instance.flow;
let stmt: ExpressionRef;
if (body.kind == NodeKind.EXPRESSION) { // () => expression
assert(!instance.isAny(CommonFlags.CONSTRUCTOR | CommonFlags.GET | CommonFlags.SET));
assert(instance.is(CommonFlags.ARROW));
stmt = this.compileExpression((<ExpressionStatement>body).expression, returnType);
flow.set(FlowFlags.RETURNS);
} else {
assert(body.kind == NodeKind.BLOCK);
stmt = this.compileStatement(body);
flow.finalize();
if (isConstructor) {
let nativeSizeType = this.options.nativeSizeType;
assert(instance.is(CommonFlags.INSTANCE));
// implicitly return `this` if the constructor doesn't always return on its own
if (!flow.is(FlowFlags.RETURNS)) {
// if all branches are guaranteed to allocate, skip the final conditional allocation
if (flow.is(FlowFlags.ALLOCATES)) {
stmt = module.createBlock(null, [
stmt,
module.createGetLocal(0, nativeSizeType)
], nativeSizeType);
// if not all branches are guaranteed to allocate, also append a conditional allocation
} else {
let parent = assert(instance.memberOf);
assert(parent.kind == ElementKind.CLASS);
stmt = module.createBlock(null, [
stmt,
module.createTeeLocal(0,
makeConditionalAllocate(this, <Class>parent, declaration.name)
)
], nativeSizeType);
}
}
// make sure all branches return
let allBranchesReturn = flow.finalize();
if (returnType != Type.void && !allBranchesReturn) {
} else if (returnType != Type.void && !flow.is(FlowFlags.RETURNS)) {
this.error(
DiagnosticCode.A_function_whose_declared_type_is_not_void_must_return_a_value,
declaration.signature.returnType.range
@ -1272,13 +1301,15 @@ export class Compiler extends DiagnosticEmitter {
var stmt = this.module.createBlock(null, this.compileStatements(statements), NativeType.None);
var stmtReturns = flow.is(FlowFlags.RETURNS);
var stmtThrows = flow.is(FlowFlags.THROWS);
var stmtAllocates = flow.is(FlowFlags.ALLOCATES);
// Switch back to the parent flow
flow = flow.leaveBranchOrScope();
this.currentFunction.flow = flow;
if (stmtReturns) {
flow.set(FlowFlags.RETURNS);
}
if (stmtReturns) flow.set(FlowFlags.RETURNS);
if (stmtThrows) flow.set(FlowFlags.THROWS);
if (stmtAllocates) flow.set(FlowFlags.ALLOCATES);
return stmt;
}
@ -1300,7 +1331,7 @@ export class Compiler extends DiagnosticEmitter {
);
return module.createUnreachable();
}
flow.set(FlowFlags.POSSIBLY_BREAKS);
flow.set(FlowFlags.BREAKS);
return module.createBreak(breakLabel);
}
@ -1324,7 +1355,7 @@ export class Compiler extends DiagnosticEmitter {
);
return module.createUnreachable();
}
flow.set(FlowFlags.POSSIBLY_CONTINUES);
flow.set(FlowFlags.CONTINUES);
return module.createBreak(continueLabel);
}
@ -1407,12 +1438,18 @@ export class Compiler extends DiagnosticEmitter {
? this.compileExpression(<Expression>statement.incrementor, Type.void)
: module.createNop();
var body = this.compileStatement(statement.statement);
var alwaysReturns = !statement.condition && flow.is(FlowFlags.RETURNS);
var alwaysThrows = !statement.condition && flow.is(FlowFlags.THROWS);
var alwaysAllocates = !statement.condition && flow.is(FlowFlags.ALLOCATES);
// TODO: check other always-true conditions as well, not just omitted
if (alwaysReturns) flow.set(FlowFlags.RETURNS);
if (alwaysThrows) flow.set(FlowFlags.THROWS);
if (alwaysAllocates) flow.set(FlowFlags.ALLOCATES);
// Switch back to the parent flow
flow = flow.leaveBranchOrScope();
currentFunction.flow = flow;
currentFunction.flow = flow.leaveBranchOrScope();
currentFunction.leaveBreakContext();
var expr = module.createBlock(breakLabel, [
@ -1426,9 +1463,8 @@ export class Compiler extends DiagnosticEmitter {
], NativeType.None))
], NativeType.None);
// If the loop is guaranteed to run and return, propagate that and append a hint
if (alwaysReturns) {
flow.set(FlowFlags.RETURNS);
// If the loop is guaranteed to run and return, append a hint
if (alwaysReturns || alwaysThrows) {
expr = module.createBlock(null, [
expr,
module.createUnreachable()
@ -1472,22 +1508,30 @@ export class Compiler extends DiagnosticEmitter {
currentFunction.flow = flow;
var ifTrueExpr = this.compileStatement(ifTrue);
var ifTrueReturns = flow.is(FlowFlags.RETURNS);
var ifTrueThrows = flow.is(FlowFlags.THROWS);
var ifTrueAllocates = flow.is(FlowFlags.ALLOCATES);
flow = flow.leaveBranchOrScope();
currentFunction.flow = flow;
var ifFalseExpr: ExpressionRef = 0;
var ifFalseReturns = false;
var ifFalseThrows = false;
var ifFalseAllocates = false;
if (ifFalse) {
flow = flow.enterBranchOrScope();
currentFunction.flow = flow;
ifFalseExpr = this.compileStatement(ifFalse);
ifFalseReturns = flow.is(FlowFlags.RETURNS);
ifFalseThrows = flow.is(FlowFlags.THROWS);
ifFalseAllocates = flow.is(FlowFlags.ALLOCATES);
flow = flow.leaveBranchOrScope();
currentFunction.flow = flow;
}
if (ifTrueReturns && ifFalseReturns) { // not necessary to append a hint
flow.set(FlowFlags.RETURNS);
}
if (ifTrueReturns && ifFalseReturns) flow.set(FlowFlags.RETURNS);
if (ifTrueThrows && ifFalseThrows) flow.set(FlowFlags.THROWS);
if (ifTrueAllocates && ifFalseAllocates) flow.set(FlowFlags.ALLOCATES);
return module.createIf(condExpr, ifTrueExpr, ifFalseExpr);
}
@ -1556,6 +1600,8 @@ export class Compiler extends DiagnosticEmitter {
// nest blocks in order
var currentBlock = module.createBlock("case0|" + context, breaks, NativeType.None);
var alwaysReturns = true;
var alwaysThrows = true;
var alwaysAllocates = true;
for (let i = 0; i < numCases; ++i) {
let case_ = cases[i];
let statements = case_.statements;
@ -1577,6 +1623,12 @@ export class Compiler extends DiagnosticEmitter {
if (!(fallsThrough || flow.is(FlowFlags.RETURNS))) {
alwaysReturns = false; // ignore fall-throughs
}
if (!(fallsThrough || flow.is(FlowFlags.THROWS))) {
alwaysThrows = false;
}
if (!(fallsThrough || flow.is(FlowFlags.ALLOCATES))) {
alwaysAllocates = false;
}
// Switch back to the parent flow
currentFunction.flow = flow.leaveBranchOrScope();
@ -1586,9 +1638,11 @@ export class Compiler extends DiagnosticEmitter {
currentFunction.leaveBreakContext();
// If the switch has a default and always returns, propagate that
if (defaultIndex >= 0 && alwaysReturns) {
currentFunction.flow.set(FlowFlags.RETURNS);
// Binaryen understands that so we don't need a hint
if (defaultIndex >= 0) {
let flow = currentFunction.flow;
if (alwaysReturns) flow.set(FlowFlags.RETURNS);
if (alwaysThrows) flow.set(FlowFlags.THROWS);
if (alwaysAllocates) flow.set(FlowFlags.ALLOCATES);
}
return currentBlock;
}
@ -1596,8 +1650,8 @@ export class Compiler extends DiagnosticEmitter {
compileThrowStatement(statement: ThrowStatement): ExpressionRef {
var flow = this.currentFunction.flow;
// Remember that this branch possibly throws
flow.set(FlowFlags.POSSIBLY_THROWS);
// Remember that this branch throws
flow.set(FlowFlags.THROWS);
// FIXME: without try-catch it is safe to assume RETURNS as well for now
flow.set(FlowFlags.RETURNS);
@ -1791,8 +1845,8 @@ export class Compiler extends DiagnosticEmitter {
flow.continueLabel = continueLabel;
var body = this.compileStatement(statement.statement);
var alwaysReturns = false && flow.is(FlowFlags.RETURNS);
// TODO: evaluate possible always-true conditions
var alwaysReturns = false; // CONDITION_IS_ALWAYS_TRUE && flow.is(FlowFlags.RETURNS);
// TODO: evaluate if condition is always true
// Switch back to the parent flow
currentFunction.flow = flow.leaveBranchOrScope();
@ -4471,6 +4525,18 @@ export class Compiler extends DiagnosticEmitter {
let parent = assert(currentFunction.memberOf);
assert(parent.kind == ElementKind.CLASS);
let thisType = (<Class>parent).type;
if (currentFunction.is(CommonFlags.CONSTRUCTOR)) {
let nativeSizeType = this.options.nativeSizeType;
let flow = currentFunction.flow;
if (!flow.is(FlowFlags.ALLOCATES)) {
flow.set(FlowFlags.ALLOCATES);
// must be conditional because `this` could have been provided by a derived class
this.currentType = thisType;
return module.createTeeLocal(0,
makeConditionalAllocate(this, <Class>parent, expression)
);
}
}
this.currentType = thisType;
return module.createGetLocal(0, thisType.toNativeType());
}
@ -4908,70 +4974,43 @@ export class Compiler extends DiagnosticEmitter {
var options = this.options;
var currentFunction = this.currentFunction;
// obtain the class being instantiated
var resolved = this.program.resolveExpression( // reports
expression.expression,
currentFunction
);
if (resolved) {
if (resolved.element.kind == ElementKind.CLASS_PROTOTYPE) {
let prototype = <ClassPrototype>resolved.element;
let instance = prototype.resolveUsingTypeArguments( // reports
expression.typeArguments,
null,
expression
);
if (instance) {
let thisExpr = compileBuiltinAllocate(this, instance, expression);
let initializers = new Array<ExpressionRef>();
// use a temp local for 'this'
let tempLocal = currentFunction.getTempLocal(options.usizeType);
initializers.push(module.createSetLocal(tempLocal.index, thisExpr));
// apply field initializers
if (instance.members) {
for (let member of instance.members.values()) {
if (member.kind == ElementKind.FIELD) {
let field = <Field>member;
let fieldDeclaration = field.prototype.declaration;
if (field.is(CommonFlags.CONST)) {
assert(false); // there are no built-in fields currently
} else if (fieldDeclaration && fieldDeclaration.initializer) {
initializers.push(module.createStore(field.type.byteSize,
module.createGetLocal(tempLocal.index, options.nativeSizeType),
this.compileExpression(fieldDeclaration.initializer, field.type),
field.type.toNativeType(),
field.memoryOffset
));
}
}
}
}
// apply constructor
let constructorInstance = instance.constructorInstance;
if (constructorInstance) {
initializers.push(this.compileCallDirect(constructorInstance, expression.arguments, expression,
module.createGetLocal(tempLocal.index, options.nativeSizeType)
));
}
// return 'this'
initializers.push(module.createGetLocal(tempLocal.index, options.nativeSizeType));
currentFunction.freeTempLocal(tempLocal);
thisExpr = module.createBlock(null, initializers, options.nativeSizeType);
this.currentType = instance.type;
return thisExpr;
}
} else {
this.error(
DiagnosticCode.Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature,
expression.expression.range
);
}
if (!resolved) return module.createUnreachable();
if (resolved.element.kind != ElementKind.CLASS_PROTOTYPE) {
this.error(
DiagnosticCode.Cannot_use_new_with_an_expression_whose_type_lacks_a_construct_signature,
expression.expression.range
);
return this.module.createUnreachable();
}
return module.createUnreachable();
var classPrototype = <ClassPrototype>resolved.element;
var classInstance = classPrototype.resolveUsingTypeArguments( // reports
expression.typeArguments,
null,
expression
);
if (!classInstance) return module.createUnreachable();
var expr: ExpressionRef;
var constructorInstance = classInstance.constructorInstance;
// if a constructor is present, call it with a zero `this`
if (constructorInstance) {
expr = this.compileCallDirect(constructorInstance, expression.arguments, expression,
options.usizeType.toNativeZero(module)
);
// otherwise simply allocate a new instance and initialize its fields
} else {
expr = makeAllocate(this, classInstance, expression);
}
this.currentType = classInstance.type;
return expr;
}
compileParenthesizedExpression(
@ -5764,3 +5803,78 @@ export function makeIsTrueish(expr: ExpressionRef, type: Type, module: Module):
}
}
}
/** Makes an allocation expression for an instance of the specified class. */
export function makeAllocate(compiler: Compiler, classInstance: Class, reportNode: Node): ExpressionRef {
var module = compiler.module;
var currentFunction = compiler.currentFunction;
var nativeSizeType = compiler.options.nativeSizeType;
var tempLocal = currentFunction.getTempLocal(classInstance.type);
// allocate the necessary memory
var initializers = new Array<ExpressionRef>();
initializers.push(
module.createSetLocal(tempLocal.index,
compileBuiltinAllocate(compiler, classInstance, reportNode)
)
);
// apply field initializers
if (classInstance.members) {
for (let member of classInstance.members.values()) {
if (member.kind == ElementKind.FIELD) {
let field = <Field>member;
let fieldType = field.type;
let fieldDeclaration = field.prototype.declaration;
assert(!field.isAny(CommonFlags.CONST));
if (fieldDeclaration.initializer) { // use initializer
initializers.push(module.createStore(fieldType.byteSize,
module.createGetLocal(tempLocal.index, nativeSizeType),
compiler.compileExpression(fieldDeclaration.initializer, fieldType), // reports
fieldType.toNativeType(),
field.memoryOffset
));
} else { // initialize with zero
// TODO: might be unnecessary if the ctor initializes the field
initializers.push(module.createStore(field.type.byteSize,
module.createGetLocal(tempLocal.index, nativeSizeType),
field.type.toNativeZero(module),
field.type.toNativeType(),
field.memoryOffset
));
}
}
}
}
// return `this`
initializers.push(
module.createGetLocal(tempLocal.index, nativeSizeType)
);
currentFunction.freeTempLocal(tempLocal);
compiler.currentType = classInstance.type;
return module.createBlock(null, initializers, nativeSizeType);
}
/** Makes a conditional allocation expression inside of the constructor of the specified class. */
export function makeConditionalAllocate(compiler: Compiler, classInstance: Class, reportNode: Node): ExpressionRef {
// requires that `this` is the first local
var module = compiler.module;
var nativeSizeType = compiler.options.nativeSizeType;
compiler.currentType = classInstance.type;
return module.createIf(
nativeSizeType == NativeType.I64
? module.createBinary(
BinaryOp.NeI64,
module.createGetLocal(0, NativeType.I64),
module.createI64(0)
)
: module.createGetLocal(0, NativeType.I32),
module.createGetLocal(0, nativeSizeType),
module.createTeeLocal(0,
makeAllocate(compiler, classInstance, reportNode)
)
);
}

View File

@ -2377,8 +2377,10 @@ export class FunctionPrototype extends Element {
}
var returnType: Type;
if (this.is(CommonFlags.SET) || this.is(CommonFlags.CONSTRUCTOR)) {
if (this.is(CommonFlags.SET)) {
returnType = Type.void; // not annotated
} else if (this.is(CommonFlags.CONSTRUCTOR)) {
returnType = assert(classInstance).type; // not annotated
} else {
let typeNode = assert(signatureNode.returnType);
let type = this.program.resolveType(typeNode, contextualTypeArguments, true); // reports
@ -3157,14 +3159,28 @@ export class Interface extends Class {
export const enum FlowFlags {
/** No specific conditions. */
NONE = 0,
/** This branch always returns. */
RETURNS = 1 << 0,
/** This branch possibly throws. */
POSSIBLY_THROWS = 1 << 1,
/** This branch possible breaks. */
POSSIBLY_BREAKS = 1 << 2,
/** This branch possible continues. */
POSSIBLY_CONTINUES = 1 << 3
/** This branch always throws. */
THROWS = 1 << 1,
/** This branch always breaks. */
BREAKS = 1 << 2,
/** This branch always continues. */
CONTINUES = 1 << 3,
/** This branch always allocates. Constructors only. */
ALLOCATES = 1 << 4,
/** This branch conditionally returns in a child branch. */
CONDITIONALLY_RETURNS = 1 << 5,
/** This branch conditionally throws in a child branch. */
CONDITIONALLY_THROWS = 1 << 6,
/** This branch conditionally breaks in a child branch. */
CONDITIONALLY_BREAKS = 1 << 7,
/** This branch conditionally continues in a child branch. */
CONDITIONALLY_CONTINUES = 1 << 8,
/** This branch conditionally allocates in a child branch. Constructors only. */
CONDITIONALLY_ALLOCATES = 1 << 9
}
/** A control flow evaluator. */
@ -3200,16 +3216,18 @@ export class Flow {
is(flag: FlowFlags): bool { return (this.flags & flag) == flag; }
/** Sets the specified flag or flags. */
set(flag: FlowFlags): void { this.flags |= flag; }
/** Unsets the specified flag or flags. */
unset(flag: FlowFlags): void { this.flags &= ~flag; }
/** Enters a new branch or scope and returns the new flow. */
enterBranchOrScope(): Flow {
var branchFlow = new Flow();
branchFlow.parent = this;
branchFlow.flags = this.flags;
branchFlow.currentFunction = this.currentFunction;
branchFlow.continueLabel = this.continueLabel;
branchFlow.breakLabel = this.breakLabel;
return branchFlow;
var branch = new Flow();
branch.parent = this;
branch.flags = this.flags;
branch.currentFunction = this.currentFunction;
branch.continueLabel = this.continueLabel;
branch.breakLabel = this.breakLabel;
return branch;
}
/** Leaves the current branch or scope and returns the parent flow. */
@ -3225,14 +3243,20 @@ export class Flow {
}
// Propagate flags to parent
if (this.is(FlowFlags.POSSIBLY_THROWS)) {
parent.set(FlowFlags.POSSIBLY_THROWS);
if (this.is(FlowFlags.RETURNS)) {
parent.set(FlowFlags.CONDITIONALLY_RETURNS);
}
if (this.is(FlowFlags.POSSIBLY_BREAKS) && parent.breakLabel == this.breakLabel) {
parent.set(FlowFlags.POSSIBLY_BREAKS);
if (this.is(FlowFlags.THROWS)) {
parent.set(FlowFlags.CONDITIONALLY_THROWS);
}
if (this.is(FlowFlags.POSSIBLY_CONTINUES) && parent.continueLabel == this.continueLabel) {
parent.set(FlowFlags.POSSIBLY_CONTINUES);
if (this.is(FlowFlags.BREAKS) && parent.breakLabel == this.breakLabel) {
parent.set(FlowFlags.CONDITIONALLY_BREAKS);
}
if (this.is(FlowFlags.CONTINUES) && parent.continueLabel == this.continueLabel) {
parent.set(FlowFlags.CONDITIONALLY_CONTINUES);
}
if (this.is(FlowFlags.ALLOCATES)) {
parent.set(FlowFlags.CONDITIONALLY_ALLOCATES);
}
return parent;
@ -3265,10 +3289,9 @@ export class Flow {
}
/** Finalizes this flow. Must be the topmost parent flow of the function. */
finalize(): bool {
finalize(): void {
assert(this.parent == null, "must be the topmost parent flow");
this.continueLabel = null;
this.breakLabel = null;
return this.is(FlowFlags.RETURNS);
}
}

12
std/assembly.d.ts vendored
View File

@ -248,7 +248,17 @@ declare function parseI64(str: string, radix?: i32): i64;
/** Parses a string to a 64-bit float. */
declare function parseFloat(str: string): f64;
// Standard library (not yet implemented)
// Standard library
/** Class representing a generic, fixed-length raw binary data buffer. */
declare class ArrayBuffer {
/** The size, in bytes, of the array. */
readonly byteLength: i32;
/** Constructs a new array buffer of the given length in bytes. */
constructor(length: i32);
/** Returns a copy of this array buffer's bytes from begin, inclusive, up to end, exclusive. */
slice(begin?: i32, end?: i32): ArrayBuffer;
}
/** Class representing a sequence of values of type `T`. */
declare class Array<T> {

View File

@ -0,0 +1,29 @@
const HEADER_SIZE: usize = sizeof<i32>();
@unmanaged
export class ArrayBuffer {
readonly byteLength: i32;
constructor(length: i32) {
if (<u32>length > 0x7fffffff) {
throw new RangeError("Invalid array buffer length");
}
var buffer = allocate_memory(HEADER_SIZE + <usize>length);
store<i32>(buffer, length);
return changetype<ArrayBuffer>(buffer);
}
slice(begin: i32 = 0, end: i32 = 0x7fffffff): ArrayBuffer {
var len = this.byteLength;
if (begin < 0) begin = max(len + begin, 0);
else begin = min(begin, len);
if (end < 0) end = max(len + end, 0);
else end = min(end, len);
var newLen = max(end - begin, 0);
var buffer = allocate_memory(HEADER_SIZE + <usize>newLen);
store<i32>(buffer, newLen);
move_memory(buffer + HEADER_SIZE, changetype<usize>(this) + HEADER_SIZE + begin, newLen);
return changetype<ArrayBuffer>(buffer);
}
}

10
std/portable.d.ts vendored
View File

@ -200,6 +200,16 @@ declare function parseFloat(str: string): f64;
declare const NaN: f32 | f64;
declare const Infinity: f32 | f64;
/** Class representing a generic, fixed-length raw binary data buffer. */
declare class ArrayBuffer {
/** The size, in bytes, of the array. */
readonly byteLength: i32;
/** Constructs a new array buffer of the given length in bytes. */
constructor(length: i32);
/** Returns a copy of this array buffer's bytes from begin, inclusive, up to end, exclusive. */
slice(begin?: i32, end?: i32): ArrayBuffer;
}
declare class Array<T> {
[key: number]: T;
length: i32;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
import "allocator/arena";
var buffer = new ArrayBuffer(8);
assert(buffer.byteLength == 8);
var sliced = buffer.slice();
assert(sliced.byteLength == 8);
assert(sliced != buffer);
sliced = buffer.slice(1);
assert(sliced.byteLength == 7);
sliced = buffer.slice(-1);
assert(sliced.byteLength == 1);
sliced = buffer.slice(1, 3);
assert(sliced.byteLength == 2);
sliced = buffer.slice(1, -1);
assert(sliced.byteLength == 6);
sliced = buffer.slice(-3, -1);
assert(sliced.byteLength == 2);
sliced = buffer.slice(-4, 42);
assert(sliced.byteLength == 4);
sliced = buffer.slice(42);
assert(sliced.byteLength == 0);
assert(sliced != null);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,273 @@
(module
(type $ii (func (param i32) (result i32)))
(type $v (func))
(global "$(lib)/allocator/arena/startOffset" (mut i32) (i32.const 0))
(global "$(lib)/allocator/arena/offset" (mut i32) (i32.const 0))
(global $std/constructor/emptyCtor (mut i32) (i32.const 0))
(global $std/constructor/emptyCtorWithFieldInit (mut i32) (i32.const 0))
(global $std/constructor/emptyCtorWithFieldNoInit (mut i32) (i32.const 0))
(global $std/constructor/none (mut i32) (i32.const 0))
(global $std/constructor/justFieldInit (mut i32) (i32.const 0))
(global $std/constructor/justFieldNoInit (mut i32) (i32.const 0))
(global $std/constructor/ctorReturns (mut i32) (i32.const 0))
(global $std/constructor/b (mut i32) (i32.const 1))
(global $std/constructor/ctorConditionallyReturns (mut i32) (i32.const 0))
(global $std/constructor/ctorAllocates (mut i32) (i32.const 0))
(global $std/constructor/ctorConditionallyAllocates (mut i32) (i32.const 0))
(global $HEAP_BASE i32 (i32.const 4))
(memory $0 1)
(export "memory" (memory $0))
(start $start)
(func "$(lib)/allocator/arena/allocate_memory" (; 0 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
(if
(i32.eqz
(get_local $0)
)
(return
(i32.const 0)
)
)
(if
(i32.gt_u
(tee_local $2
(i32.and
(i32.add
(i32.add
(tee_local $1
(get_global "$(lib)/allocator/arena/offset")
)
(get_local $0)
)
(i32.const 7)
)
(i32.const -8)
)
)
(i32.shl
(tee_local $0
(current_memory)
)
(i32.const 16)
)
)
(if
(i32.lt_s
(grow_memory
(select
(get_local $0)
(tee_local $4
(tee_local $3
(i32.shr_u
(i32.and
(i32.add
(i32.sub
(get_local $2)
(get_local $1)
)
(i32.const 65535)
)
(i32.const -65536)
)
(i32.const 16)
)
)
)
(i32.gt_s
(get_local $0)
(get_local $4)
)
)
)
(i32.const 0)
)
(if
(i32.lt_s
(grow_memory
(get_local $3)
)
(i32.const 0)
)
(unreachable)
)
)
)
(set_global "$(lib)/allocator/arena/offset"
(get_local $2)
)
(get_local $1)
)
(func $std/constructor/EmptyCtor#constructor (; 1 ;) (type $ii) (param $0 i32) (result i32)
(if (result i32)
(get_local $0)
(get_local $0)
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
(func $std/constructor/EmptyCtorWithFieldInit#constructor (; 2 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(if (result i32)
(get_local $0)
(get_local $0)
(block (result i32)
(i32.store
(tee_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.const 1)
)
(get_local $1)
)
)
)
(func $std/constructor/EmptyCtorWithFieldNoInit#constructor (; 3 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(if (result i32)
(get_local $0)
(get_local $0)
(block (result i32)
(i32.store
(tee_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.const 0)
)
(get_local $1)
)
)
)
(func $std/constructor/CtorReturns#constructor (; 4 ;) (type $ii) (param $0 i32) (result i32)
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(func $std/constructor/CtorConditionallyReturns#constructor (; 5 ;) (type $ii) (param $0 i32) (result i32)
(if
(get_global $std/constructor/b)
(return
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
(if (result i32)
(get_local $0)
(get_local $0)
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
(func $std/constructor/CtorConditionallyAllocates#constructor (; 6 ;) (type $ii) (param $0 i32) (result i32)
(if
(get_global $std/constructor/b)
(if
(i32.eqz
(get_local $0)
)
(set_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
)
(if (result i32)
(get_local $0)
(get_local $0)
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
(func $start (; 7 ;) (type $v)
(local $0 i32)
(set_global "$(lib)/allocator/arena/startOffset"
(i32.and
(i32.add
(get_global $HEAP_BASE)
(i32.const 7)
)
(i32.const -8)
)
)
(set_global "$(lib)/allocator/arena/offset"
(get_global "$(lib)/allocator/arena/startOffset")
)
(set_global $std/constructor/emptyCtor
(call $std/constructor/EmptyCtor#constructor
(i32.const 0)
)
)
(set_global $std/constructor/emptyCtorWithFieldInit
(call $std/constructor/EmptyCtorWithFieldInit#constructor
(i32.const 0)
)
)
(set_global $std/constructor/emptyCtorWithFieldNoInit
(call $std/constructor/EmptyCtorWithFieldNoInit#constructor
(i32.const 0)
)
)
(set_global $std/constructor/none
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(set_global $std/constructor/justFieldInit
(block (result i32)
(i32.store
(tee_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.const 1)
)
(get_local $0)
)
)
(set_global $std/constructor/justFieldNoInit
(block (result i32)
(i32.store
(tee_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.const 0)
)
(get_local $0)
)
)
(set_global $std/constructor/ctorReturns
(call $std/constructor/CtorReturns#constructor
(i32.const 0)
)
)
(set_global $std/constructor/ctorConditionallyReturns
(call $std/constructor/CtorConditionallyReturns#constructor
(i32.const 0)
)
)
(set_global $std/constructor/ctorAllocates
(call $std/constructor/EmptyCtor#constructor
(i32.const 0)
)
)
(set_global $std/constructor/ctorConditionallyAllocates
(call $std/constructor/CtorConditionallyAllocates#constructor
(i32.const 0)
)
)
)
)

View File

@ -0,0 +1,86 @@
import "allocator/arena";
// trailing conditional allocate
class EmptyCtor {
constructor() {}
}
var emptyCtor = new EmptyCtor();
// trailing conditional allocate with field initializer
class EmptyCtorWithFieldInit {
a: i32 = 1;
constructor() {}
}
var emptyCtorWithFieldInit = new EmptyCtorWithFieldInit();
// trailing conditional allocate with field initialized to zero
class EmptyCtorWithFieldNoInit {
a: i32;
constructor() {}
}
var emptyCtorWithFieldNoInit = new EmptyCtorWithFieldNoInit();
// direct allocate
class None {
}
var none = new None();
// direct allocate with field initializer
class JustFieldInit {
a: i32 = 1;
}
var justFieldInit = new JustFieldInit();
// direct allocate with field initialized to zero
class JustFieldNoInit {
a: i32;
}
var justFieldNoInit = new JustFieldNoInit();
// explicit allocation with no extra checks
class CtorReturns {
constructor() {
return changetype<CtorReturns>(allocate_memory(0));
}
}
var ctorReturns = new CtorReturns();
var b: bool = true;
// explicit allocation with a trailing conditional fallback
class CtorConditionallyReturns {
constructor() {
if (b) {
return changetype<CtorConditionallyReturns>(allocate_memory(0));
}
}
}
var ctorConditionallyReturns = new CtorConditionallyReturns();
// implicit allocation with no extra checks
class CtorAllocates {
constructor() {
this;
}
}
var ctorAllocates = new CtorAllocates();
// implicit allocation with a trailing conditional fallback
class CtorConditionallyAllocates {
constructor() {
if (b) {
this;
}
}
}
var ctorConditionallyAllocates = new CtorConditionallyAllocates();

View File

@ -0,0 +1,392 @@
(module
(type $i (func (result i32)))
(type $ii (func (param i32) (result i32)))
(type $v (func))
(global "$(lib)/allocator/common/alignment/BITS" i32 (i32.const 3))
(global "$(lib)/allocator/common/alignment/SIZE" i32 (i32.const 8))
(global "$(lib)/allocator/common/alignment/MASK" i32 (i32.const 7))
(global "$(lib)/allocator/arena/startOffset" (mut i32) (i32.const 0))
(global "$(lib)/allocator/arena/offset" (mut i32) (i32.const 0))
(global $std/constructor/emptyCtor (mut i32) (i32.const 0))
(global $std/constructor/emptyCtorWithFieldInit (mut i32) (i32.const 0))
(global $std/constructor/emptyCtorWithFieldNoInit (mut i32) (i32.const 0))
(global $std/constructor/none (mut i32) (i32.const 0))
(global $std/constructor/justFieldInit (mut i32) (i32.const 0))
(global $std/constructor/justFieldNoInit (mut i32) (i32.const 0))
(global $std/constructor/ctorReturns (mut i32) (i32.const 0))
(global $std/constructor/b (mut i32) (i32.const 1))
(global $std/constructor/ctorConditionallyReturns (mut i32) (i32.const 0))
(global $std/constructor/ctorAllocates (mut i32) (i32.const 0))
(global $std/constructor/ctorConditionallyAllocates (mut i32) (i32.const 0))
(global $HEAP_BASE i32 (i32.const 4))
(memory $0 1)
(export "memory" (memory $0))
(start $start)
(func "$(lib)/allocator/arena/allocate_memory" (; 0 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(local $2 i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
(local $6 i32)
(if
(i32.eqz
(get_local $0)
)
(return
(i32.const 0)
)
)
(set_local $1
(get_global "$(lib)/allocator/arena/offset")
)
(set_local $2
(i32.and
(i32.add
(i32.add
(get_local $1)
(get_local $0)
)
(i32.const 7)
)
(i32.xor
(i32.const 7)
(i32.const -1)
)
)
)
(set_local $3
(current_memory)
)
(if
(i32.gt_u
(get_local $2)
(i32.shl
(get_local $3)
(i32.const 16)
)
)
(block
(set_local $4
(i32.shr_u
(i32.and
(i32.add
(i32.sub
(get_local $2)
(get_local $1)
)
(i32.const 65535)
)
(i32.xor
(i32.const 65535)
(i32.const -1)
)
)
(i32.const 16)
)
)
(set_local $5
(select
(tee_local $5
(get_local $3)
)
(tee_local $6
(get_local $4)
)
(i32.gt_s
(get_local $5)
(get_local $6)
)
)
)
(if
(i32.lt_s
(grow_memory
(get_local $5)
)
(i32.const 0)
)
(if
(i32.lt_s
(grow_memory
(get_local $4)
)
(i32.const 0)
)
(unreachable)
)
)
)
)
(set_global "$(lib)/allocator/arena/offset"
(get_local $2)
)
(return
(get_local $1)
)
)
(func $std/constructor/EmptyCtor#constructor (; 1 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(block
)
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(get_local $1)
)
)
)
)
)
(func $std/constructor/EmptyCtorWithFieldInit#constructor (; 2 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(block
)
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.store
(get_local $1)
(i32.const 1)
)
(get_local $1)
)
)
)
)
)
(func $std/constructor/EmptyCtorWithFieldNoInit#constructor (; 3 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(block
)
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.store
(get_local $1)
(i32.const 0)
)
(get_local $1)
)
)
)
)
)
(func $std/constructor/CtorReturns#constructor (; 4 ;) (type $ii) (param $0 i32) (result i32)
(return
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
(func $std/constructor/CtorConditionallyReturns#constructor (; 5 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(block
(if
(get_global $std/constructor/b)
(return
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
)
)
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(get_local $1)
)
)
)
)
)
(func $std/constructor/CtorAllocates#constructor (; 6 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(block
(drop
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(get_local $1)
)
)
)
)
)
)
(get_local $0)
)
(func $std/constructor/CtorConditionallyAllocates#constructor (; 7 ;) (type $ii) (param $0 i32) (result i32)
(local $1 i32)
(block
(if
(get_global $std/constructor/b)
(drop
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(get_local $1)
)
)
)
)
)
)
)
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $1
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(get_local $1)
)
)
)
)
)
(func $start (; 8 ;) (type $v)
(local $0 i32)
(set_global "$(lib)/allocator/arena/startOffset"
(i32.and
(i32.add
(get_global $HEAP_BASE)
(i32.const 7)
)
(i32.xor
(i32.const 7)
(i32.const -1)
)
)
)
(set_global "$(lib)/allocator/arena/offset"
(get_global "$(lib)/allocator/arena/startOffset")
)
(set_global $std/constructor/emptyCtor
(call $std/constructor/EmptyCtor#constructor
(i32.const 0)
)
)
(set_global $std/constructor/emptyCtorWithFieldInit
(call $std/constructor/EmptyCtorWithFieldInit#constructor
(i32.const 0)
)
)
(set_global $std/constructor/emptyCtorWithFieldNoInit
(call $std/constructor/EmptyCtorWithFieldNoInit#constructor
(i32.const 0)
)
)
(set_global $std/constructor/none
(block (result i32)
(set_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 0)
)
)
(get_local $0)
)
)
(set_global $std/constructor/justFieldInit
(block (result i32)
(set_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.store
(get_local $0)
(i32.const 1)
)
(get_local $0)
)
)
(set_global $std/constructor/justFieldNoInit
(block (result i32)
(set_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 4)
)
)
(i32.store
(get_local $0)
(i32.const 0)
)
(get_local $0)
)
)
(set_global $std/constructor/ctorReturns
(call $std/constructor/CtorReturns#constructor
(i32.const 0)
)
)
(set_global $std/constructor/ctorConditionallyReturns
(call $std/constructor/CtorConditionallyReturns#constructor
(i32.const 0)
)
)
(set_global $std/constructor/ctorAllocates
(call $std/constructor/CtorAllocates#constructor
(i32.const 0)
)
)
(set_global $std/constructor/ctorConditionallyAllocates
(call $std/constructor/CtorConditionallyAllocates#constructor
(i32.const 0)
)
)
)
)

View File

@ -1,6 +1,6 @@
(module
(type $ifi (func (param i32 f32) (result i32)))
(type $ii (func (param i32) (result i32)))
(type $ifv (func (param i32 f32)))
(type $v (func))
(global "$(lib)/allocator/arena/startOffset" (mut i32) (i32.const 0))
(global "$(lib)/allocator/arena/offset" (mut i32) (i32.const 0))
@ -91,12 +91,33 @@
)
(get_local $1)
)
(func $std/new/AClass#constructor (; 1 ;) (type $ifv) (param $0 i32) (param $1 f32)
(func $std/new/AClass#constructor (; 1 ;) (type $ifi) (param $0 i32) (param $1 f32) (result i32)
(local $2 i32)
(i32.store
(get_local $0)
(i32.add
(i32.load
(get_local $0)
(if (result i32)
(get_local $0)
(get_local $0)
(block (result i32)
(i32.store
(tee_local $2
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 8)
)
)
(i32.const 1)
)
(f32.store offset=4
(get_local $2)
(f32.const 2)
)
(tee_local $0
(get_local $2)
)
)
)
)
(i32.const 1)
)
@ -105,9 +126,9 @@
(get_local $0)
(get_local $1)
)
(get_local $0)
)
(func $start (; 2 ;) (type $v)
(local $0 i32)
(set_global "$(lib)/allocator/arena/startOffset"
(i32.and
(i32.add
@ -121,24 +142,9 @@
(get_global "$(lib)/allocator/arena/startOffset")
)
(set_global $std/new/aClass
(block (result i32)
(i32.store
(tee_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 8)
)
)
(i32.const 1)
)
(f32.store offset=4
(get_local $0)
(f32.const 2)
)
(call $std/new/AClass#constructor
(get_local $0)
(f32.const 3)
)
(get_local $0)
(call $std/new/AClass#constructor
(i32.const 0)
(f32.const 3)
)
)
)

View File

@ -1,7 +1,7 @@
(module
(type $i (func (result i32)))
(type $ifi (func (param i32 f32) (result i32)))
(type $ii (func (param i32) (result i32)))
(type $ifv (func (param i32 f32)))
(type $v (func))
(global "$(lib)/allocator/common/alignment/BITS" i32 (i32.const 3))
(global "$(lib)/allocator/common/alignment/SIZE" i32 (i32.const 8))
@ -116,23 +116,49 @@
(get_local $1)
)
)
(func $std/new/AClass#constructor (; 1 ;) (type $ifv) (param $0 i32) (param $1 f32)
(i32.store
(get_local $0)
(i32.add
(i32.load
(get_local $0)
(func $std/new/AClass#constructor (; 1 ;) (type $ifi) (param $0 i32) (param $1 f32) (result i32)
(local $2 i32)
(block
(i32.store
(get_local $0)
(i32.add
(i32.load
(tee_local $0
(if (result i32)
(get_local $0)
(get_local $0)
(tee_local $0
(block (result i32)
(set_local $2
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 8)
)
)
(i32.store
(get_local $2)
(i32.const 1)
)
(f32.store offset=4
(get_local $2)
(f32.const 2)
)
(get_local $2)
)
)
)
)
)
(i32.const 1)
)
(i32.const 1)
)
(f32.store offset=4
(get_local $0)
(get_local $1)
)
)
(f32.store offset=4
(get_local $0)
(get_local $1)
)
(get_local $0)
)
(func $start (; 2 ;) (type $v)
(local $0 i32)
(set_global "$(lib)/allocator/arena/startOffset"
(i32.and
(i32.add
@ -149,25 +175,9 @@
(get_global "$(lib)/allocator/arena/startOffset")
)
(set_global $std/new/aClass
(block (result i32)
(set_local $0
(call "$(lib)/allocator/arena/allocate_memory"
(i32.const 8)
)
)
(i32.store
(get_local $0)
(i32.const 1)
)
(f32.store offset=4
(get_local $0)
(f32.const 2)
)
(call $std/new/AClass#constructor
(get_local $0)
(f32.const 3)
)
(get_local $0)
(call $std/new/AClass#constructor
(i32.const 0)
(f32.const 3)
)
)
)