Skip to content

Commit

Permalink
implement first class function
Browse files Browse the repository at this point in the history
  • Loading branch information
HerrCai0907 committed Nov 10, 2023
1 parent f055bff commit 758e86a
Show file tree
Hide file tree
Showing 15 changed files with 4,855 additions and 48 deletions.
35 changes: 29 additions & 6 deletions src/builtins.ts
Expand Up @@ -43,7 +43,8 @@ import {
NodeKind,
LiteralExpression,
ArrayLiteralExpression,
IdentifierExpression
IdentifierExpression,
FunctionExpression
} from "./ast";

import {
Expand Down Expand Up @@ -194,6 +195,8 @@ export namespace BuiltinNames {
export const instantiate = "~lib/builtins/instantiate";
export const idof = "~lib/builtins/idof";

export const experimental_first_class_function = "~lib/builtins/experimental_first_class_function";

export const i8 = "~lib/builtins/i8";
export const i16 = "~lib/builtins/i16";
export const i32 = "~lib/builtins/i32";
Expand Down Expand Up @@ -3609,6 +3612,26 @@ function builtin_unchecked(ctx: BuiltinFunctionContext): ExpressionRef {
}
builtinFunctions.set(BuiltinNames.unchecked, builtin_unchecked);

// experimental_first_class_function(FunctionExpression: *) -> *
function builtin_experimental_first_class_function(ctx: BuiltinFunctionContext): ExpressionRef {
let compiler = ctx.compiler;
let module = compiler.module;
if (
checkTypeAbsent(ctx) |
checkArgsRequired(ctx, 1)
) return module.unreachable();
let operand = ctx.operands[0];
if (operand.kind != NodeKind.Function) {
let prototype = ctx.prototype;
prototype.program.error(DiagnosticCode._0_expected, operand.range, "FunctionExpression");
return module.unreachable();
}
let functionExpression = <FunctionExpression>operand;
let expr = compiler.compileFirstClassFunction(functionExpression);
return expr;
}
builtinFunctions.set(BuiltinNames.experimental_first_class_function, builtin_experimental_first_class_function);

// call_indirect<T?>(index: u32, ...args: *[]) -> T
function builtin_call_indirect(ctx: BuiltinFunctionContext): ExpressionRef {
let compiler = ctx.compiler;
Expand Down Expand Up @@ -10668,11 +10691,11 @@ export function compileVisitGlobals(compiler: Compiler): void {
let global = <Global>element;
let globalType = global.type;
let classReference = globalType.getClass();
if (
classReference &&
!classReference.hasDecorator(DecoratorFlags.Unmanaged) &&
global.is(CommonFlags.Compiled)
) {
let signatureReference = globalType.getSignature();
let isGcObject =
(classReference != null && !classReference.hasDecorator(DecoratorFlags.Unmanaged)) ||
(signatureReference != null && signatureReference.hasEnv);
if (isGcObject && global.is(CommonFlags.Compiled)) {
if (global.is(CommonFlags.Inlined)) {
let value = global.constantIntegerValue;
if (i64_low(value) || i64_high(value)) {
Expand Down
2 changes: 2 additions & 0 deletions src/common.ts
Expand Up @@ -158,6 +158,7 @@ export namespace CommonNames {
export const valueof = "valueof";
export const returnof = "returnof";
export const nonnull = "nonnull";
export const experimental_first_class_function = "experimental_first_class_function";
// aliases
export const null_ = "null";
export const true_ = "true";
Expand Down Expand Up @@ -225,6 +226,7 @@ export namespace CommonNames {
export const Set = "Set";
export const Map = "Map";
export const Function = "Function";
export const FirstClassFunction = "FirstClassFunctionBase";
export const ArrayBufferView = "ArrayBufferView";
export const ArrayBuffer = "ArrayBuffer";
export const Math = "Math";
Expand Down
59 changes: 48 additions & 11 deletions src/compiler.ts
Expand Up @@ -188,7 +188,8 @@ import {
TypeKind,
TypeFlags,
Signature,
typesToRefs
typesToRefs,
SignatureFlags
} from "./types";

import {
Expand Down Expand Up @@ -2054,20 +2055,23 @@ export class Compiler extends DiagnosticEmitter {

// === Table ====================================================================================

private registerFunctionInTable(instance: Function): u32 {
// Add to the function table
let functionTable = this.functionTable;
let tableBase = this.options.tableBase;
if (!tableBase) tableBase = 1; // leave first elem blank
let index = tableBase + functionTable.length;
functionTable.push(instance);
return index;
}

/** Ensures that a runtime counterpart of the specified function exists and returns its address. */
ensureRuntimeFunction(instance: Function): i64 {
assert(instance.is(CommonFlags.Compiled) && !instance.is(CommonFlags.Stub));
let program = this.program;
let memorySegment = instance.memorySegment;
if (!memorySegment) {

// Add to the function table
let functionTable = this.functionTable;
let tableBase = this.options.tableBase;
if (!tableBase) tableBase = 1; // leave first elem blank
let index = tableBase + functionTable.length;
functionTable.push(instance);

let index = this.registerFunctionInTable(instance);
// Create runtime function
let rtInstance = assert(this.resolver.resolveClass(program.functionPrototype, [ instance.type ]));
let buf = rtInstance.createBuffer();
Expand Down Expand Up @@ -6085,12 +6089,13 @@ export class Compiler extends DiagnosticEmitter {
let functionArg = this.compileExpression(expression.expression, Type.auto);
let signature = this.currentType.getSignature();
if (signature) {
const thisArg = signature.hasEnv ? functionArg : 0;
return this.compileCallIndirect(
signature,
functionArg,
expression.args,
expression,
0,
thisArg,
contextualType == Type.void
);
}
Expand Down Expand Up @@ -7038,6 +7043,38 @@ export class Compiler extends DiagnosticEmitter {
return module.unreachable();
}

compileFirstClassFunction(
expression: FunctionExpression
): ExpressionRef {
let module = this.module;
let flow = this.currentFlow;
let sourceFunction = flow.sourceFunction;
let declaration = expression.declaration.clone();
let anonymousId = sourceFunction.nextAnonymousId++;
let contextualTypeArguments = cloneMap(flow.contextualTypeArguments);

let prototype = new FunctionPrototype(
`${sourceFunction.internalName}|anonymous|${anonymousId}`,
sourceFunction,
declaration
);
let instance = this.resolver.resolveFirstClassFunction(prototype, contextualTypeArguments, ReportMode.Report);
if (!instance) return this.module.unreachable();
instance.flow.outer = flow;

let worked = this.compileFunction(instance);
this.currentType = instance.signature.type;
if (!worked) return module.unreachable();

const currentType = this.currentType;
if (!instance) return module.unreachable();
const functionIndexInTable = this.registerFunctionInTable(instance);
let ctor = this.ensureConstructor(this.program.firstClassFunctionInstance, expression);
let expr = this.makeCallDirect(ctor, [module.i32(0), module.i32(functionIndexInTable), module.usize(0)], expression, /* immediatelyDropped */ false);
this.currentType = currentType;
return expr;
}

private compileFunctionExpression(
expression: FunctionExpression,
contextualType: Type,
Expand Down Expand Up @@ -8774,7 +8811,7 @@ export class Compiler extends DiagnosticEmitter {
classInstance.type,
classInstance.type,
baseCtor.signature.requiredParameters,
baseCtor.signature.hasRest
baseCtor.signature.hasRest ? SignatureFlags.Rest : SignatureFlags.None
),
contextualTypeArguments
);
Expand Down
14 changes: 14 additions & 0 deletions src/program.ts
Expand Up @@ -538,6 +538,14 @@ export class Program extends DiagnosticEmitter {
}
private _functionPrototype: ClassPrototype | null = null;

/** Get the first class `Function` instance */
get firstClassFunctionInstance(): Class {
let cached = this._firstClassFunctionInstance;
if (!cached) this._firstClassFunctionInstance = cached = this.requireClass(CommonNames.FirstClassFunction);
return cached;
}
private _firstClassFunctionInstance: Class | null = null;

/** Gets the standard `Int8Array` prototype. */
get int8ArrayPrototype(): ClassPrototype {
let cached = this._int8ArrayPrototype;
Expand Down Expand Up @@ -1010,6 +1018,12 @@ export class Program extends DiagnosticEmitter {
this.makeNativeTypeDeclaration(CommonNames.nonnull, CommonFlags.Export | CommonFlags.Generic),
DecoratorFlags.Builtin
));
this.nativeFile.add(CommonNames.experimental_first_class_function, new TypeDefinition(
CommonNames.experimental_first_class_function,
this.nativeFile,
this.makeNativeTypeDeclaration(CommonNames.experimental_first_class_function, CommonFlags.Export | CommonFlags.Generic),
DecoratorFlags.Builtin
));

// The following types might not be enabled by compiler options, so the
// compiler needs to check this condition whenever such a value is created
Expand Down

0 comments on commit 758e86a

Please sign in to comment.