Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support first class function via builtin function #2752

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
15 changes: 14 additions & 1 deletion 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 Expand Up @@ -3741,7 +3755,6 @@ export class FunctionPrototype extends DeclaredElement {

/** A resolved function. */
export class Function extends TypedElement {

/** Function prototype. */
prototype: FunctionPrototype;
/** Function signature. */
Expand Down