From 1a5fcfafd8114ee56fcd071ac80d1e1acc2371d9 Mon Sep 17 00:00:00 2001 From: dvtate Date: Sat, 18 Nov 2023 22:10:37 -0600 Subject: [PATCH] wat debugging --- lib/context.ts | 13 +- lib/expr/branch.ts | 37 +++--- lib/expr/closure.ts | 16 +-- lib/expr/enum.ts | 50 ++++---- lib/expr/expr.ts | 149 ++++++++++++++++++++++- lib/expr/fun.ts | 19 +-- lib/expr/gc_util.ts | 141 ---------------------- lib/expr/index.ts | 1 + lib/expr/objects.ts | 36 ++++++ lib/expr/recursion.ts | 54 +++++---- lib/expr/util.ts | 37 +++--- lib/globals.ts | 3 +- lib/module.ts | 15 ++- lib/rt.wat | 5 +- lib/value.ts | 16 ++- lib/wat.ts | 167 ++++++++++++++++++++------ planning/raytracing_demo/material.phs | 2 +- planning/raytracing_demo/ray.phs | 2 +- planning/raytracing_demo/raytrace.phs | 11 +- planning/raytracing_demo/scene.phs | 2 +- planning/raytracing_demo/sphere.phs | 4 +- planning/raytracing_demo/vec3.phs | 4 +- planning/rec.phs | 17 +++ tools/file.ts | 2 +- 24 files changed, 487 insertions(+), 316 deletions(-) create mode 100644 lib/expr/objects.ts create mode 100644 planning/rec.phs diff --git a/lib/context.ts b/lib/context.ts index 4885223..0101550 100644 --- a/lib/context.ts +++ b/lib/context.ts @@ -679,12 +679,17 @@ export default class Context { const invalid = Boolean(mod.validate()); if (invalid) { console.error('invalid:', invalid); - console.log(src); + // console.log(src); return; } - } catch (e) { - console.log('validate error:', src); - throw e; + } catch (e: any) { + console.log('Generated WAT did not pass validator:', e); + + const lineNum = Number(e.message.split('\n')[1].split(':')[1]) - 1460; + this.module.src.debug(lineNum - 10, lineNum); + + // throw e; + process.exit(); } } diff --git a/lib/expr/branch.ts b/lib/expr/branch.ts index b343b55..dc60a7c 100644 --- a/lib/expr/branch.ts +++ b/lib/expr/branch.ts @@ -5,6 +5,7 @@ import ModuleManager from '../module.js'; import { DataExpr, Expr } from './expr.js'; import type { FunExpr, FunLocalTracker } from './fun.js'; import { DependentLocalExpr } from './util.js'; +import wat, { WatCode } from '../wat.js'; /** * Describes expensive expressions which were on the stack before a branch was invoked @@ -33,22 +34,22 @@ export class BranchInputExpr extends DataExpr { * @param fun * @returns WAT */ - capture(ctx: ModuleManager, fun: FunExpr) { + capture(ctx: ModuleManager, fun: FunExpr): WatCode { // if (this.index) // return ''; this.index = fun.addLocal(this.value.datatype); - return `${this.value.out(ctx, fun)}\n${fun.setLocalWat(this.index)}`; + return wat(this)`${this.value.out(ctx, fun)}\n${fun.setLocalWat(this.index)}`; } /** * @override */ - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { if (!this._datatype.isUnit() && !this.index) { console.log('branch input: ', this.value); console.log(new Error('bt')); } - return fun.getLocalWat(this.index); + return wat(this)`${fun.getLocalWat(this.index)}`; } /** @@ -123,20 +124,23 @@ export class BranchExpr extends Expr { /** * @override */ - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // Prevent multiple compilations this._isCompiled = true; - const inputs = this.inputExprs.map(e => e.capture(ctx, fun)).join('\n\t'); + // const inputs = this.inputExprs.map(e => e.capture(ctx, fun)).join('\n\t'); + const inputs = new WatCode().concat( + ...this.inputExprs.map(e => e.capture(ctx, fun))); // Compile body // Notice order of compilation from top to bottom so that locals are assigned before use // TODO FIXME still not perfect... may need to convert tee-exprs - const conds = new Array(this.conditions.length); - const acts = new Array(this.actions.length); + const conds = new Array(this.conditions.length); + const acts = new Array(this.actions.length); for (let i = this.conditions.length - 1; i >= 0; i--) { const invIdx = (this.conditions.length - i) - 1; conds[invIdx] = this.conditions[i].out(ctx, fun); - acts[invIdx] = this.actions[i].reverse().map(a => a.out(ctx, fun)).join(' '); + acts[invIdx] = new WatCode() + .concat(...this.actions[i].reverse().map(a => a.out(ctx, fun))); } // const conds = this.conditions.map(c => c.out(ctx, fun)).reverse(); // const acts = this.actions.map(a => a.map(v => v.out(ctx, fun)).join(' ')).reverse(); @@ -148,8 +152,8 @@ export class BranchExpr extends Expr { this.results.forEach(r => r.setInds(fun)); // Last condition must be else clause - if (conds[conds.length - 1] != '(i32.const 1)') { - // console.log(conds[conds.length - 1]); + if (conds[conds.length - 1].toString() != '(i32.const 1)') { + console.log(conds[conds.length - 1].toString()); throw new error.SyntaxError("no else case for fun branch", this.tokens); } @@ -180,19 +184,20 @@ export class BranchExpr extends Expr { // Compile to (if (...) (result ...) (then ...) (else ...)) // Note that there's some BS done here to work around multi-return if statements not being allowed :( const retSet = this.results.map(r => fun.setLocalWat(r.getInds())).join(''); - let ret: string = inputs + (function compileIf(i): string { + const self = this as Expr; + let ret: WatCode = inputs.concat((function compileIf(i): WatCode { return i + 1 >= acts.length - ? acts[i] + retSet - : `${conds[i]}\n\t(if ${ + ? wat(self)`${acts[i]}${retSet}` + : wat(self as any)`${conds[i]}\n\t(if ${ retType.length === 1 ? `(result ${retType})` : '' }\n\t(then ${ acts[i] + retSet })\n\t(else ${ compileIf(i + 1) }))`; - })(0); + })(0)); if (retType.length === 1) - ret += '\n\t' + retSet; + ret.add(this, '\n\t' + retSet); // console.log('BranchExpr', ret); return ret; diff --git a/lib/expr/closure.ts b/lib/expr/closure.ts index 4d2d06e..3324f21 100644 --- a/lib/expr/closure.ts +++ b/lib/expr/closure.ts @@ -11,6 +11,7 @@ import { InternalFunExpr, FunExpr, ParamExpr } from './fun.js'; import { LiteralMacro, Macro } from '../macro.js'; import { DependentLocalExpr, ProxyExpr, TeeExpr } from './util.js'; import Context from '../context.js'; +import wat, { WatCode } from '../wat.js'; /** * Capture lexically scoped variables and store them into a new closure object @@ -96,7 +97,7 @@ export class ClosureCreateExpr extends DataExpr { this.func.nparams = 1 + this.params.length; } - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // Add function to module table once only if (!this._isCompiled) { ctx.addFunction(this.func); @@ -107,7 +108,7 @@ export class ClosureCreateExpr extends DataExpr { // this is super painful ... maybe check to see if they've been compiled yet? // maybe extend expressions? - return ''; + return new WatCode(); } children(): Expr[] { throw new Error('todo'); @@ -131,7 +132,7 @@ export class ClosureInvokeExpr extends Expr { new DependentLocalExpr(token, t as types.DataType, this)); } - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // DependentLocalExpr checks this this._isCompiled = true; @@ -139,10 +140,11 @@ export class ClosureInvokeExpr extends Expr { // And invoke function via function table // The first argument being the closure object this.outputs.forEach(r => r.setInds(fun)); - let ret = this.closure.out(ctx, fun); - ret += '(i32.load (global.get $__ref_sp)) (call_indirect)'; - ret += this.outputs.map(r => fun.setLocalWat(r.getInds())); - return ret; + return wat(this)`${ + this.closure.out(ctx, fun) + }(i32.load (global.get $__ref_sp)) (call_indirect)${ + this.outputs.map(r => fun.setLocalWat(r.getInds())).join('') + }`; } invoke(ctx: Context) { diff --git a/lib/expr/enum.ts b/lib/expr/enum.ts index 404c7d1..fbb27db 100644 --- a/lib/expr/enum.ts +++ b/lib/expr/enum.ts @@ -5,11 +5,11 @@ import type { LexerToken } from '../scan.js'; import type ModuleManager from '../module.js'; import { Expr, DataExpr } from './expr.js'; import type { FunExpr } from './fun.js'; -import { constructGc, loadRef } from './gc_util.js'; import { DependentLocalExpr, fromDataValue } from './util.js'; import { BranchInputExpr } from './branch.js'; import { uid } from '../util.js'; import Context from '../context.js'; +import wat, { WatCode } from '../wat.js'; // Check if an enum contains specified value // export class EnumContainsCheckExpr extends DataExpr { @@ -37,7 +37,7 @@ import Context from '../context.js'; // // Need to determine dynamically // if (eedt instanceof types.EnumBaseType) // return `${this.enumExpr.out(ctx, fun) -// }\n\t(call $__ref_stack_pop)(i32.load)(i32.const ${ +// } (call $__ref_stack_pop)(i32.load)(i32.const ${ // this.checkType.index})(i32.eq)`; // throw new SyntaxError('Not cannot check if non-enum contains type', [this.enumExpr.token, this.token]); @@ -57,17 +57,19 @@ export class EnumGetExpr extends DataExpr { super(token, dt.type); } - out(ctx: ModuleManager, fun?: FunExpr) { + out(ctx: ModuleManager, fun?: FunExpr): WatCode { if (!this.results) - return this.enumExpr.out(ctx, fun) - + '(drop)' - + loadRef(new types.RefType(this.token, this._datatype), fun); + return wat(this)`${ + this.enumExpr.out(ctx, fun) + }(drop)${ + this.loadRef(new types.RefType(this.token, this._datatype), fun) + }`; this.results.forEach(r => r.inds = fun.addLocal(r.datatype)); - return `${ + return wat(this)`${ this.enumExpr.out(ctx, fun) } (drop) ${ - loadRef(new types.RefType(this.token, this._datatype), fun) + this.loadRef(new types.RefType(this.token, this._datatype), fun) } ${ this.results.map(r => fun.setLocalWat(r.inds)).join(' ') }`; @@ -113,7 +115,7 @@ export class EnumTypeIndexExpr extends DataExpr { return this.enumExpr.children(); } - out(ctx: ModuleManager, fun?: FunExpr) { + out(ctx: ModuleManager, fun?: FunExpr): WatCode { // let eedt = this.enumExpr.datatype; // if (eedt instanceof types.ClassType) // eedt = eedt.getBaseType(); @@ -123,9 +125,9 @@ export class EnumTypeIndexExpr extends DataExpr { // Simply discard the reference - return this.enumExpr.out(ctx, fun) + return wat(this)`${this.enumExpr.out(ctx, fun) + }(global.set $__ref_sp (i32.add (global.get $__ref_sp) (i32.const 4)))`; // equiv (call $__ref_stack_pop) (drop) - + '(global.set $__ref_sp (i32.add (global.get $__ref_sp) (i32.const 4)))' } } @@ -144,13 +146,13 @@ export class EnumConstructor extends DataExpr { super(token, enumClassType); } - out(ctx: ModuleManager, fun?: FunExpr): string { + out(ctx: ModuleManager, fun?: FunExpr): WatCode { const v = this.knownValue instanceof value.Value ? fromDataValue([this.knownValue]) : this.knownValue; - return `(i32.const ${this._datatype.index})\n\t${ - v.map(v => v.out(ctx, fun)).join(' ') - }\n\t${constructGc(this.enumClassType.type, ctx, fun)}`; + return wat(this)`(i32.const ${String(this._datatype.index)}) ${ + v.map(v => v.out(ctx, fun)) + } ${this.constructGc(this.enumClassType.type, ctx, fun)}`; } children(): Expr[] { @@ -189,27 +191,27 @@ export class EnumMatchExpr extends Expr { // .concat(...this.results.map(r => r.children())); } - out(ctx: ModuleManager, fun: FunExpr): string { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // Prevent multiple compilations this._isCompiled = true; - let ret = this.inputs.map(inp => inp.capture(ctx, fun)).join('\n\t'); + const inpWat = this.inputs.map(inp => inp.capture(ctx, fun)) this.results.forEach(r => r.inds = fun.addLocal(r.datatype)); const retType = this.outputTypes.map(t => t.getWasmTypeName()).join(' '); const branchId = `$branch_${uid()}`; - ret += `(block ${branchId} (result ${retType}) ${ + const ret = wat(this)`${inpWat}(block ${branchId} (result ${retType}) ${ this.branches.slice(1).map(() => '(block ').join('') } (block (block ${ this.typeIndexExpr.out(ctx, fun) - }\n\t (br_table ${ + } (br_table ${ this.branchBindings.map(n => String(n + 1)).join(' ') } 0)) unreachable) ${ - this.branches - .map(vs => vs.map(v => v.out(ctx, fun)).join(' ')) - .join(`(br ${branchId}) )`) - } )`; - ret += this.results.map(dl => fun.setLocalWat(dl.inds)); + wat.join(this, `(br ${branchId}) )`, + ...this.branches.map(vs => vs.map(v => v.out(ctx, fun)))) + } ) ${this.results.map(dl => fun.setLocalWat(dl.inds)).join(' ')}`; + + console.log('this should error if not 1 or 0: ', this.results.length) return ret; } diff --git a/lib/expr/expr.ts b/lib/expr/expr.ts index 0afa4b1..832acca 100644 --- a/lib/expr/expr.ts +++ b/lib/expr/expr.ts @@ -2,7 +2,8 @@ import * as value from '../value.js'; import * as types from '../datatypes.js'; import type { LexerToken } from '../scan.js'; import type ModuleManager from '../module.js'; -import type { FunExpr } from './fun.js'; +import type { FunExpr, FunLocalTracker } from './fun.js'; +import type { WatCode } from '../wat.js'; // This file defines the abstract base types for expressions @@ -13,7 +14,7 @@ import type { FunExpr } from './fun.js'; * Some values are compatible */ export interface Compileable { - out(ctx: ModuleManager, fun?: FunExpr): string; + out(ctx: ModuleManager, fun?: FunExpr): WatCode; children(): Expr[]; datatype?: types.DataType; } @@ -46,7 +47,7 @@ export abstract class Expr extends value.Value { * @param fun - function export context * @returns - wasm translation */ - abstract out(ctx: ModuleManager, fun?: FunExpr): string; + abstract out(ctx: ModuleManager, fun?: FunExpr): WatCode; /** * Get all expressions which constitute this one @@ -90,6 +91,148 @@ export abstract class Expr extends value.Value { return [...ret]; } + + + /** + * Size of primitve datatype, otherwise assume it's a reference thus sizeof(i32) => 4 + */ + private static primDtSize(t: types.PrimitiveType | types.RefType) { + if (t instanceof types.PrimitiveType) + switch (t.name) { + case 'i32': case 'f32': return 4; + case 'i64': case 'f64': return 8; + default: throw new Error('wtf?'); + } + return 4; + } + + /** + * Create the reference bitfield used by garbage collector when tracing + * + * See the documentation on linear memory and garbage collection in planning/implementation/lm.md + * + * @param dt datatype to add bitfield for + * @param fpl primitive components for the datatype dt.flatPrimitiveList() + * @param sizes sizes for the components of the datatype + * @returns bits constituting the bitfield + */ + static genGcBitfield( + dt: types.DataType, + fpl = dt.flatPrimitiveList(), + sizes = fpl.map(this.primDtSize), + ): Uint8Array { + // Generate bitstring + const bfStr = fpl.map((t, i) => + t instanceof types.PrimitiveType + ? sizes[i] === 4 + ? '0' : '00' + : '1' + ).join(''); + + // Convert bitstring to int8 array + const ret: number[] = []; + let i = 0; + while (i < bfStr.length) { + let b = 0; + const ni = i + 7; + for (; i < ni; i++) { + if (bfStr[i] === '1') + b++; + b <<= 1; + } + ret.push(b); + } + + return new Uint8Array(ret); + } + + /** + * Construct a gc'd object + * @param ctx compiler context + * @param fun function containing object construction + * @returns wat + */ + protected constructGc(dt: types.DataType, ctx: ModuleManager, fun: FunExpr): string { + // No reason to allocate object for unit values + const fpl = dt.flatPrimitiveList(); + if (fpl.length == 0) + return '(call $__ref_stack_push (i32.const 0))'; + + // Get reference to gc'd object + const fpSizes = fpl.map(Expr.primDtSize).reverse(); + const bf = Expr.genGcBitfield(dt, fpl, fpSizes); + const bfAddr = ctx.addStaticData(bf, true); + let ret = `\n\t(call $__alloc (i32.const ${bf.length}) (i32.const ${bfAddr}))`; + + // Store raw gc reference into local + // NOTE we could probably put it directly into the rv stack and wbbuff + // should give similar perf and improve thread safety + const local = fun.addLocal(types.PrimitiveType.Types.I32)[0]; + ret += local.setLocalWat(); + ret += '\n\t'; + + // Copy object into heap + const locals: { [k: string]: FunLocalTracker[] } = {}; // Recycle locals of same types + let totalSize = fpSizes.reduce((a, b) => a + b, 0); + fpl.reverse().forEach((t, i) => { + // Swap addr with last component of object before using store instruction + // Webassembly is poorly designed, the addr should be second arg to t.store + if (t instanceof types.PrimitiveType) { + // Primitive + const swapLocal = locals[t.name] || (locals[t.name] = fun.addLocal(t)); + ret += `${fun.setLocalWat(swapLocal) + }${local.getLocalWat() + }${fun.getLocalWat(swapLocal) + }(${t.name}.store offset=${totalSize -= fpSizes[i]})`; + } else { + // Use Reference from ref stack + ret += `${local.getLocalWat() + }(call $__ref_stack_pop)(i32.store offset=${totalSize -= fpSizes[i]})`; + } + }); + + // Push gc reference onto ref stack for safety + ret += `(call $__ref_stack_push ${local.getLocalWat()})`; + + // Free up temporary locals + ret += fun.removeLocalWat([local].concat(...Object.values(locals))); + return ret; + } + + /** + * Load object pointed to by reference onto the stack + * @param dt type of value to load + * @param fun function it's being loaded into + * @returns wasm text source + */ + protected loadRef( + dt: types.RefType, + fun: FunExpr, + ): string { + const fpl = dt.unpackRefs(); + + // Single object + if (fpl.length <= 1) + return fpl.map(t => `(${t.type.getWasmTypeName()}.load offset=${t.offsetBytes} (call $__ref_stack_pop))${ + t.type instanceof types.RefType ? '(call $__ref_stack_push)' : '' }` + ).join(' '); + + // Store pointer into a local for multiple uses + const ptrLocal = fun.addLocal(types.PrimitiveType.Types.I32); + return `(call $__ref_stack_pop)${ + fun.setLocalWat(ptrLocal) + }${ + fpl.map(t => + `(${t.type.getWasmTypeName()}.load offset=${t.offsetBytes} ${fun.getLocalWat(ptrLocal)})${ + t.type instanceof types.RefType ? '(call $__ref_stack_push)' : '' }` + ).reverse().join(' ') + }${fun.removeLocalWat(ptrLocal)}`; + + // After this point `ptrLocal` is no longer needed so I could + // add a `fun.freeLocal()` method which allows it to get used to hold other values + // not a big deal for primitives since simple optimizers will catch but for + // references it would definitely pay off + } } /** diff --git a/lib/expr/fun.ts b/lib/expr/fun.ts index 16bf082..cd53378 100644 --- a/lib/expr/fun.ts +++ b/lib/expr/fun.ts @@ -5,6 +5,7 @@ import type ModuleManager from '../module.js'; import { LexerToken } from '../scan.js'; import { Expr, DataExpr } from './expr.js'; import { uid } from '../util.js'; +import wat, { WatCode } from '../wat.js'; // Methods for storing and accessing locals @@ -234,8 +235,8 @@ export abstract class FunExpr extends Expr { * @param body original body * @returns wrapped body */ - protected wrapBody(body: string): string { - return `\n\t(local ${ + protected wrapBody(...body: WatCode[]): WatCode { + return wat(this)`\n\t(local ${ this.locals.slice(this.nparams).map(l => l.watTypename).join(' ') })\n\t${ this.rvStackOffset ? `(global.set $__rv_sp (i32.sub (global.get $__rv_sp) (i32.const ${this.rvStackOffset})))` @@ -343,18 +344,18 @@ export abstract class FunExpr extends Expr { export class InternalFunExpr extends FunExpr { // TODO should make apis to help lift nested functions/closures - out(ctx: ModuleManager): string { + out(ctx: ModuleManager): WatCode { // TODO tuples const outs = this.outputs.map(o => o.out(ctx, this)); const paramTypes = this.locals.slice(0, this.nparams).map(t => t.datatype.getWasmTypeName()).join(' '); const resultTypes = this.outputs.map(r => r.datatype.getWasmTypeName()).filter(Boolean).join(' '); - return `(func $${this.name} ${ + return wat(this)`(func $${this.name} ${ paramTypes ? `(param ${paramTypes})` : '' } ${ resultTypes ? `(result ${resultTypes})` : '' } ${ - this.wrapBody(outs.join('\n\t')) + this.wrapBody(...outs) })`; } @@ -372,8 +373,8 @@ export class FunExportExpr extends InternalFunExpr { /** * @override */ - out(ctx: ModuleManager): string { - return `${super.out(ctx)}\n(export "${this.name}" (func $${this.name}))`; + out(ctx: ModuleManager): WatCode { + return wat(this)`${super.out(ctx)}\n(export "${this.name}" (func $${this.name}))`; } children(): Expr[] { @@ -417,7 +418,7 @@ export class ParamExpr extends DataExpr { /** * @override */ - out() { - return this.source.getLocalWat(this.inds); + out(): WatCode { + return new WatCode(this, this.source.getLocalWat(this.inds)); } } diff --git a/lib/expr/gc_util.ts b/lib/expr/gc_util.ts index 1d283a0..b2037fe 100644 --- a/lib/expr/gc_util.ts +++ b/lib/expr/gc_util.ts @@ -2,144 +2,3 @@ import * as types from '../datatypes.js'; import type ModuleManager from '../module.js'; import type { FunExpr, FunLocalTracker } from './fun.js'; - -/** - * Size of primitve datatype, otherwise assume it's a reference thus sizeof(i32) => 4 - */ - function primDtSize(t: types.PrimitiveType | types.RefType) { - if (t instanceof types.PrimitiveType) - switch (t.name) { - case 'i32': case 'f32': return 4; - case 'i64': case 'f64': return 8; - default: throw new Error('wtf?'); - } - return 4; -} - -/** - * Create the reference bitfield used by garbage collector when tracing - * - * See the documentation on linear memory and garbage collection in planning/implementation/lm.md - * - * @param dt datatype to add bitfield for - * @param fpl primitive components for the datatype dt.flatPrimitiveList() - * @param sizes sizes for the components of the datatype - * @returns bits constituting the bitfield - */ -export function genGcBitfield( - dt: types.DataType, - fpl = dt.flatPrimitiveList(), - sizes = fpl.map(primDtSize), -): Uint8Array { - // Generate bitstring - const bfStr = fpl.map((t, i) => - t instanceof types.PrimitiveType - ? sizes[i] === 4 - ? '0' : '00' - : '1' - ).join(''); - - // Convert bitstring to int8 array - const ret: number[] = []; - let i = 0; - while (i < bfStr.length) { - let b = 0; - const ni = i + 7; - for (; i < ni; i++) { - if (bfStr[i] === '1') - b++; - b <<= 1; - } - ret.push(b); - } - - return new Uint8Array(ret); -} - -/** - * Construct a gc'd object - * @param ctx compiler context - * @param fun function containing object construction - * @returns wat - */ -export function constructGc(dt: types.DataType, ctx: ModuleManager, fun: FunExpr): string { - // No reason to allocate object for unit values - const fpl = dt.flatPrimitiveList(); - if (fpl.length == 0) - return '(call $__ref_stack_push (i32.const 0))'; - - // Get reference to gc'd object - const fpSizes = fpl.map(primDtSize).reverse(); - const bf = genGcBitfield(dt, fpl, fpSizes); - const bfAddr = ctx.addStaticData(bf, true); - let ret = `\n\t(call $__alloc (i32.const ${bf.length}) (i32.const ${bfAddr}))`; - - // Store raw gc reference into local - // NOTE we could probably put it directly into the rv stack and wbbuff - // should give similar perf and improve thread safety - const local = fun.addLocal(types.PrimitiveType.Types.I32)[0]; - ret += local.setLocalWat(); - ret += '\n\t'; - - // Copy object into heap - const locals: { [k: string]: FunLocalTracker[] } = {}; // Recycle locals of same types - let totalSize = fpSizes.reduce((a, b) => a + b, 0); - fpl.reverse().forEach((t, i) => { - // Swap addr with last component of object before using store instruction - // Webassembly is poorly designed, the addr should be second arg to t.store - if (t instanceof types.PrimitiveType) { - // Primitive - const swapLocal = locals[t.name] || (locals[t.name] = fun.addLocal(t)); - ret += `${fun.setLocalWat(swapLocal) - }${local.getLocalWat() - }${fun.getLocalWat(swapLocal) - }(${t.name}.store offset=${totalSize -= fpSizes[i]})`; - } else { - // Use Reference from ref stack - ret += `${local.getLocalWat() - }(call $__ref_stack_pop)(i32.store offset=${totalSize -= fpSizes[i]})`; - } - }); - - // Push gc reference onto ref stack for safety - ret += `(call $__ref_stack_push ${local.getLocalWat()})`; - - // Free up temporary locals - ret += fun.removeLocalWat([local].concat(...Object.values(locals))); - return ret; -} - -/** - * Load object pointed to by reference onto the stack - * @param dt type of value to load - * @param fun function it's being loaded into - * @returns wasm text source - */ -export function loadRef( - dt: types.RefType, - fun: FunExpr, -): string { - const fpl = dt.unpackRefs(); - - // Single object - if (fpl.length <= 1) - return fpl.map(t => `(${t.type.getWasmTypeName()}.load offset=${t.offsetBytes} (call $__ref_stack_pop))${ - t.type instanceof types.RefType ? '(call $__ref_stack_push)' : '' }` - ).join(' '); - - // Store pointer into a local for multiple uses - const ptrLocal = fun.addLocal(types.PrimitiveType.Types.I32); - return `(call $__ref_stack_pop)${ - fun.setLocalWat(ptrLocal) - }${ - fpl.map(t => - `(${t.type.getWasmTypeName()}.load offset=${t.offsetBytes} ${fun.getLocalWat(ptrLocal)})${ - t.type instanceof types.RefType ? '(call $__ref_stack_push)' : '' }` - ).reverse().join(' ') - }${fun.removeLocalWat(ptrLocal)}`; - - // After this point `ptrLocal` is no longer needed so I could - // add a `fun.freeLocal()` method which allows it to get used to hold other values - // not a big deal for primitives since simple optimizers will catch but for - // references it would definitely pay off -} \ No newline at end of file diff --git a/lib/expr/index.ts b/lib/expr/index.ts index 0f8f26e..661b6c2 100644 --- a/lib/expr/index.ts +++ b/lib/expr/index.ts @@ -5,6 +5,7 @@ export * from './util.js'; export * from './enum.js'; export * from './fun.js'; export * from './closure.js'; +// export * from './objects.js'; /* * This directory contains datatypes related to a graph IR used to output webassembly diff --git a/lib/expr/objects.ts b/lib/expr/objects.ts new file mode 100644 index 0000000..5b9b6c7 --- /dev/null +++ b/lib/expr/objects.ts @@ -0,0 +1,36 @@ +// import * as types from '../datatypes.js'; +// import * as value from '../value.js'; +// import * as error from '../error.js'; +// import { Expr, DataExpr } from './expr.js'; +// import type { FunExpr } from './fun.js'; +// import { constructGc, loadRef } from './gc_util.js'; +// import type ModuleManager from '../module.js'; + +// /** +// * Create a tuple object that get stored in the runtime stack +// */ +// export class TupleObjExpr extends DataExpr { +// declare value: DataExpr[]; + +// constructor(token: LexerToken, ctx: Context, v: value.TupleValue) { +// super(token, v.datatype); +// this.value = fromDataValue(v.value, ctx); +// } + +// get expensive(): boolean { +// return false; +// } + +// out(ctx: ModuleManager, fun?: FunExpr): string { + +// return this.value.map(v => v.out(ctx, fun)).join(' '); +// } + +// children(): Expr[] { +// return this.value; +// } + +// toValue(): value.Value { +// return new value.TupleValue(this.token, this.value); +// } +// } diff --git a/lib/expr/recursion.ts b/lib/expr/recursion.ts index 714bc0e..7a4c0ea 100644 --- a/lib/expr/recursion.ts +++ b/lib/expr/recursion.ts @@ -6,6 +6,7 @@ import { Expr, DataExpr } from './expr.js'; import { TeeExpr, DependentLocalExpr, } from './util.js'; import { FunExpr, ParamExpr } from './fun.js'; import { BranchExpr } from './branch.js'; +import wat, { WatCode } from '../wat.js'; /** * Used to wrap arguments passed to recursive functions as they are being traced in a way that @@ -90,7 +91,7 @@ export class RecursiveBodyExpr extends Expr { this.label = `$rec_${this.id}`; } - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // Prevent multiple compilations this._isCompiled = true; @@ -107,20 +108,20 @@ export class RecursiveBodyExpr extends Expr { // Store inputs in locals this.takeExprs.forEach(e => e.setInds(fun)); - let ret = `\n\t${this.takeExprs.map((e, i) => - `${this.takes[i].out(ctx, fun)}${fun.setLocalWat(e.getInds())}` - ).join('\n\t')}\n\t`; + let ret: WatCode = wat(this)`\n\t${wat.join(this, '\n', ...this.takeExprs.map((e, i) => + wat(this)`${this.takes[i].out(ctx, fun)}${fun.setLocalWat(e.getInds())}` + ))}\n\t`; // Create place to store outputs this.giveExprs.forEach(e => e.setInds(fun)); // Body const retType = this.gives.map(e => e.datatype.getWasmTypeName()).join(' '); - ret += `(loop ${this.label} (result ${retType})\n\t${ - this.gives.map(e => e.out(ctx, fun)).join('\n\t') + ret.concat(wat(this)`(loop ${this.label} (result ${retType})\n\t${ + wat.join(this, '\n\t', ...this.gives.map(e => e.out(ctx, fun))) })\n\t${ this.giveExprs.map(e => fun.setLocalWat(e.getInds())).join(' ') - }\n\t`; + }\n\t`); // console.log('RecursiveBodyExpr', ret); return ret; @@ -168,10 +169,10 @@ export class RecursiveBodyExpr extends Expr { this.giveExprs.forEach(e => e.setInds(fun)); // Invoke helper function and capture return values into dependent locals - return `${ - this.takes.map(e => e.out(ctx, fun)).join('') + return wat(this)`${ + this.takes.map(e => e.out(ctx, fun)) }${ - captureExprs.map((e: DataExpr) => e.out(ctx, fun)).join('') + captureExprs.map((e: DataExpr) => e.out(ctx, fun)) }\n\t(call ${this.label})${ this.giveExprs.map(e => fun.setLocalWat(e.getInds())).join('') }`; @@ -269,7 +270,7 @@ export class RecFunExpr extends FunExpr { this.copiedParams = copiedParams.filter(e => !e.datatype.isUnit()); } - out(ctx: ModuleManager): string { + out(ctx: ModuleManager): WatCode { // Capture original positions so that we can revert later so that old references don't break const originalIndicies = this.copiedParams.map(e => e.inds); @@ -291,7 +292,7 @@ export class RecFunExpr extends FunExpr { const resultTypes = this.outputs.map(r => r.datatype.getWasmTypeName()).filter(Boolean).join(' '); // Generate output wat - const ret = `(func ${this.name} ${ + const ret = wat(this)`(func ${this.name} ${ // Parameter types paramTypes ? `(param ${paramTypes})` : '' } ${ @@ -299,13 +300,13 @@ export class RecFunExpr extends FunExpr { resultTypes ? `(result ${resultTypes})` : '' } ${ // Write body - this.wrapBody(`${ + this.wrapBody(wat(this)`${ // Passed references are on the ref stack, store them in rv stack this.setLocalWat( [...this.takeExprs, ...this.copiedParams] .map(e => e.inds || []).reduce((a, b) => a.concat(b), []) .filter(l => l.datatype instanceof types.RefType)) - } ${outs.join('\n\n')}`) + } ${outs}`) })`; // Revert modifications to the exprs so that other places they're referenced don't break @@ -342,23 +343,22 @@ export class RecursiveCallExpr extends Expr { new RecursiveResultExpr(token, e.datatype, this, i)); } - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // Prevent recompiling this._isCompiled = true; // TCO behavior if (this.body.isTailRecursive) { // console.log('call', this.giveExprs); - // Set arg locals - let ret = `\n\t${this.takeExprs.map((e, i) => - `${e.out(ctx, fun)}${ + // Set arg locals & invoke + const self: RecursiveCallExpr = this; + let ret: WatCode = wat(self)`\n\t${this.takeExprs.map((e, i) => + wat(self)`${e.out(ctx, fun)}${ !this.body.takeExprs[i] || !this.body.takeExprs[i].getInds() ? '' : fun.setLocalWat(this.body.takeExprs[i].getInds())}` - ).join('\n\t')}\n\t`; + )}\n\t(br ${this.body.label})`; - // Invoke function - ret += `(br ${this.body.label})`; // console.log('RecursiveCallExpr', ret); return ret; } @@ -368,11 +368,13 @@ export class RecursiveCallExpr extends Expr { // Call helper function // Note this will always be in the body of the helper function and thus a recursive call - return `\n\t${ - this.takeExprs.map(e => e.out(ctx, fun)).join(' ') + const ret = wat(this)`\n\t${ + this.takeExprs.map(e => e.out(ctx, fun)) } ${ - this.body.helper.copiedParams.map((p: DataExpr) => p.out(ctx, fun)).join('') + this.body.helper.copiedParams.map((p: DataExpr) => p.out(ctx, fun)) } (call ${this.body.label})`; + console.log('lable: ', this.body.label); + return ret; } /** @@ -410,9 +412,9 @@ export class RecursiveResultExpr extends DataExpr { } out(ctx: ModuleManager, fun: FunExpr) { - let ret = ''; + let ret = new WatCode(); if (!this.source._isCompiled) - ret += this.source.out(ctx, fun); + ret.concat(this.source.out(ctx, fun)); // When tail-recursive we don't care about intermediate results if (this.source.body.isTailRecursive) diff --git a/lib/expr/util.ts b/lib/expr/util.ts index 3e9657a..50eac71 100644 --- a/lib/expr/util.ts +++ b/lib/expr/util.ts @@ -7,6 +7,7 @@ import type ModuleManager from '../module.js'; import Context from '../context.js'; import { EnumValue } from '../enum.js'; import { FunExpr, FunLocalTracker, FunLocalTrackerStored, InternalFunExpr } from './fun.js'; +import wat, { WatCode } from '../wat.js'; /** * Flatten a list of mixed values+expressions into a single list of expressions @@ -36,7 +37,7 @@ export function fromDataValue(vs: Array, ctx?: Context): throw new error.TypeError("incompatible value", [v.token], [v], null); }).reduce( (a: DataExpr[], v: DataExpr | DataExpr[]) => - v instanceof Array ? a.concat(v) : (a.push(v), a), + v instanceof Array ? a.concat(v) : (a.push(v), a), // shouldn't this be a.concat(...v) ??? [], ); } @@ -44,8 +45,8 @@ export function fromDataValue(vs: Array, ctx?: Context): // Provide default implementation of .out for values // TODO this is ghetto... fuck ESM // TODO this should go in Value class -value.Value.prototype.out = function (ctx: ModuleManager, fun?: FunExpr): string { - return fromDataValue([this]).map(e => e.out(ctx, fun)).join(' '); +value.Value.prototype.out = function (ctx: ModuleManager, fun?: FunExpr): WatCode { + return new WatCode().concat(...fromDataValue([this]).map(e => e.out(ctx, fun))); }; @@ -68,10 +69,10 @@ export class NumberExpr extends DataExpr { * @override */ out() { - const outValue = (v: value.Value): string => + const outValue = (v: value.Value): WatCode => v instanceof value.TupleValue - ? v.value.map(outValue).join() - : v.value.toWAST(); + ? new WatCode().concat(...v.value.map(outValue)) + : new WatCode(this, v.value.toWAST()); return outValue(this.value); } @@ -169,7 +170,7 @@ export class DependentLocalExpr extends DataExpr { out(ctx: ModuleManager, fun: FunExpr) { // source.out() will update our index to be valid and capture relevant values // into our local - const ret = `${ + const ret: WatCode = wat(this)`${ !this.source._isCompiled ? this.source.out(ctx, fun) : '' } ${fun.getLocalWat(this.inds)}`; this.source._isCompiled = true; @@ -201,13 +202,11 @@ export class InstrExpr extends DataExpr { /** * @override */ - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // See implementation for seq in standard library if (this.instr.length === 0) - return this.args.map(e => e.out(ctx, fun)).join('\n\t'); - const ret = `(${this.instr} ${this.args.map(e => e.out(ctx, fun)).join(' ')})`; - // console.log(this.constructor.name, ret); - return ret; + return new WatCode().concat(...this.args.map(e => e.out(ctx, fun))); + return wat(this)`(${this.instr} ${wat.join(this, ' ', ...this.args.map(e => e.out(ctx, fun)))})`; } /** @@ -244,7 +243,7 @@ export class TeeExpr extends DataExpr { /** * @override */ - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { // TODO why is this commented out?? // if (!this.value.expensive) // return this.value.out(ctx, fun); @@ -254,12 +253,12 @@ export class TeeExpr extends DataExpr { if (this.inds.length === 1 && this.inds[0].datatype instanceof types.PrimitiveType && this.inds[0] instanceof FunLocalTrackerStored) - return `${this.value.out(ctx, fun)}\n\t(local.tee ${this.inds[0].index})`; + return wat(this)`${this.value.out(ctx, fun)}\n\t(local.tee ${String(this.inds[0].index)})`; else - return `${this.value.out(ctx, fun)}\n\t${fun.setLocalWat(this.inds) + return wat(this)`${this.value.out(ctx, fun)}\n\t${fun.setLocalWat(this.inds) }\n\t${fun.getLocalWat(this.inds)}`; } - return fun.getLocalWat(this.inds); + return new WatCode(this, fun.getLocalWat(this.inds)); } finalize(ctx: ModuleManager, fun: FunExpr) { @@ -304,7 +303,7 @@ export class MultiInstrExpr extends Expr { /** * @override */ - out(ctx: ModuleManager, fun: FunExpr) { + out(ctx: ModuleManager, fun: FunExpr): WatCode { this._isCompiled = true; // Get locals @@ -314,7 +313,7 @@ export class MultiInstrExpr extends Expr { }); // Instruction + capture results - return `(${this.instr} ${ + return wat(this)`(${this.instr} ${ this.args.map(e => e.out(ctx, fun)).join(' ') })\n${ this.results.map(e => fun.setLocalWat(e.getInds())).join(' ') @@ -471,7 +470,7 @@ export class DummyDataExpr extends DataExpr { /** * @override */ - out(): string { + out(): WatCode { throw new Error('Invalid Compile-Time only Expr: ' + this.constructor.name); } children(): Expr[] { diff --git a/lib/globals.ts b/lib/globals.ts index 54ca4ad..415f08e 100644 --- a/lib/globals.ts +++ b/lib/globals.ts @@ -13,7 +13,6 @@ import { ActionRet, CompilerMacro, LiteralMacro, Macro } from './macro.js'; import { invokeAsm } from './asm.js'; import { EnumNs, EnumValue } from './enum.js'; import { EnumMatchExpr } from './expr/index.js'; -import { genGcBitfield } from './expr/gc_util.js'; import stdlibs from '../std/index.js'; // function fromDataValue(params: value.Value[]): DataExpr[] { @@ -811,7 +810,7 @@ const operators : MacroOperatorsSpec = { return ['expected a type']; if (!(v.value instanceof types.DataType)) return ['type argument must be representable on hardware']; - const bf = genGcBitfield(v.value); + const bf = expr.Expr.genGcBitfield(v.value); const ret = new value.StrValue(token, bf); ctx.push(ret); }, diff --git a/lib/module.ts b/lib/module.ts index 7616688..72bf2cb 100644 --- a/lib/module.ts +++ b/lib/module.ts @@ -5,6 +5,7 @@ import * as error from './error.js'; // Import WAST template as a string import template, { noRuntime as noRuntimeTemplate } from "./rt.wat.js"; +import wat, { WatCode } from "./wat.js"; /** * Adjust compiler behavior @@ -77,7 +78,7 @@ export default class ModuleManager { /** * Primarily function exports. Compiled functions and stuff that go in main body of module */ - definitions: string[] = []; + definitions: WatCode[] = []; /** * Used to generate unique importIds @@ -111,6 +112,8 @@ export default class ModuleManager { */ protected noRuntime: boolean; + public src: WatCode; + /** * @param ctx - parser context object * @param opts - compilation options @@ -183,7 +186,7 @@ export default class ModuleManager { // Note this has bad performance if (helperId.startsWith('__swap_')) { const [t1, t2] = helperId.slice(7).split('_'); - this.definitions.push(`(func $${helperId + this.definitions.push(wat(null)`(func $${helperId } (param ${t1} ${t2}) (result ${t2} ${t1 }) (local.get 1) (local.get 0))`); this.definedHelpers.add(helperId); @@ -227,13 +230,15 @@ export default class ModuleManager { } ${ i.type.getWasmTypeName(i.importId) })`) - .join('\n') - ).join('\n\n'); + .join('') + ).join(''); // Insert user-generated code into our runtime + const defs = this.definitions.filter(Boolean); + this.src = new WatCode(null).concat(...this.definitions); return this.generateRuntime( importDefs, - this.definitions.filter(Boolean).join('\n\n'), + this.src.toString(), ); // Create module as string (no runtime) diff --git a/lib/rt.wat b/lib/rt.wat index 60173d5..39ff2d4 100644 --- a/lib/rt.wat +++ b/lib/rt.wat @@ -1,7 +1,4 @@ (module - ;; Imported functions from the host - {{USER_IMPORTS}} - ;; Function reference table {{USER_TABLE}} @@ -1457,5 +1454,7 @@ ;; (export "free" (func $__heap_free)) ;; (export "coalesce" (func $__coalesce)) + ;; Imported functions from the host && user code + {{USER_IMPORTS}} {{USER_CODE_STR}} ) \ No newline at end of file diff --git a/lib/value.ts b/lib/value.ts index 9770f34..10b1ba7 100644 --- a/lib/value.ts +++ b/lib/value.ts @@ -5,6 +5,7 @@ import type ModuleManager from './module.js'; import type * as expr from './expr/index.js'; import type Namespace from './namespace.js'; import * as types from './datatypes.js'; // If we actually have to import datatypes here it will not work +import wat, { WatCode } from './wat.js'; // TODO should move these to /expr/value.ts so that cyclic imports are less anal @@ -62,7 +63,7 @@ export class Value { * @param ctx compilation context * @param fun function export body */ - out?(ctx: ModuleManager, fun?: expr.FunExpr): string; + out?(ctx: ModuleManager, fun?: expr.FunExpr): WatCode; // out(ctx: ModuleManager, fun?: expr.FunExpr): string { // return expr.fromDataValue([this]).map(e => e.out(ctx, fun)).join(' '); // } @@ -97,14 +98,16 @@ export class Value { /** * Data with a user-level type, this includes numbers, tuples and classes */ -export class DataValue extends Value { - declare _datatype: types.Type; +export abstract class DataValue extends Value implements expr.Compileable { + declare _datatype: types.DataType; type: ValueType.Data = ValueType.Data; constructor(token: LexerToken, type: types.Type, value: any) { super(token, ValueType.Data, value, type); } + abstract out(ctx: ModuleManager, fun?: expr.FunExpr): WatCode; + get datatype(): typeof this._datatype { return this._datatype; } @@ -117,6 +120,7 @@ export class DataValue extends Value { * Primitive data, native wasm types */ export class NumberValue extends DataValue { + declare value: WasmNumber; declare _datatype: types.ClassOrType; constructor(token: LexerToken, wasmNumber: WasmNumber) { @@ -126,8 +130,8 @@ export class NumberValue extends DataValue { /** * See code in expr/expr.ts */ - out(): string { - return this.value.toWAST(); + out(): WatCode { + return new WatCode(this, this.value.toWAST()); } /** @@ -181,7 +185,7 @@ export class TupleValue extends DataValue { return [].concat(...this.value.map(v => v.children && v.children())); } out(ctx: ModuleManager, fun?: expr.FunExpr) { - return this.value.map(v => v.out(ctx, fun)).join(''); + return new WatCode().concat(...this.value.map(v => v.out(ctx, fun))); } get datatype(): typeof this._datatype { diff --git a/lib/wat.ts b/lib/wat.ts index 05ea05b..7367cbd 100644 --- a/lib/wat.ts +++ b/lib/wat.ts @@ -1,22 +1,104 @@ + import * as expr from './expr/index.js'; +import type * as value from './value.js' // Not sure if I'll ever actually end up using this... + +class SourceOrigin { + code: string; + source: expr.Expr | value.Value; +} + + /** * This way we can easily identify where all the expressions originated from */ -export class WATCode { - parts: string[] = []; - sources: expr.Expr[] = []; +export class WatCode { + /** + * WAT source code + */ + protected parts: string[] = []; - add(s: string, e: expr.Expr) { - this.parts.push(s); + /** + * Expressions where this source code is defined + */ + protected sources: Array = []; + + /** + * Hierarchical construction of code + */ + protected nested: Array = []; + + /** + * @param e expression + * @param strs strings for expression + */ + constructor(e?: expr.Expr | value.Value, ...strs: string[]) { + if (e && strs.length !== 0) { + this.parts = strs; + this.sources = strs.map(_ => e); + } + } + + /** + * Add new snippet + * @param s source code + * @param e source expression + */ + add(e: expr.Expr | value.Value, s: string) { this.sources.push(e); + this.parts.push(s); + return this; + } + + concat(...others: WatCode[]): this { + others.forEach(other => { + this.parts.push(...other.parts); + this.sources.push(...other.sources); + }); + return this; } - concat(other: WATCode) { - this.parts.push(...other.parts); - this.sources.push(...other.sources); + toString() { + return this.parts.join(''); + } + + + debug(startLine?:number, endLine?: number) { + let i = 0, lineNum = 0; + + // console.log(this.parts.length, this.parts.map(p => p.length)); + + if (startLine !== undefined) { + let pln = lineNum; + for (; i < this.parts.length && lineNum < startLine; i++) { + pln = lineNum; + for (let c = 0; c < this.parts[i].length; c++) + if (this.parts[i][c] === '\n') { + lineNum++; + } + } + lineNum = pln; + } + + + if (startLine === undefined) + for (; i < this.parts.length; i++) + console.log(this.parts[i].length > 30 ? this.parts[i].slice(0, 25) + '...' : this.parts[i], '\t\t', this.sources[i].constructor); + else { + endLine = endLine || (startLine + 1); + for (; i < this.parts.length; i++) { + console.log(this.parts[i].length > 30 ? this.parts[i].slice(0, 25) + '...' : this.parts[i], '\t\t', this.sources[i].constructor); + for (let c = 0; c < this.parts[i].length; c++) + if (this.parts[i][c] === '\n') { + lineNum++; + console.log('line'); + } + if (lineNum > endLine) + break; + } + } } } @@ -24,39 +106,50 @@ export class WATCode { * this is a parametric template string literal * @param e - expression source */ -export default function wat(e: expr.Expr) { - return (strs: string[], ...bindings: WATCode[]): WATCode => +export default function wat(e: expr.Expr | value.Value) { + return (strs: TemplateStringsArray, ...bindings: Array): WatCode => strs.reduce((a, s, i) => { - a.add(s, e); - if (bindings[i - 1]) - a.concat(bindings[i - 1]); + function addItem(item: WatCode | string | WatCode[]) { + if (item) { + if (typeof item === 'string') + a.add(e, item as string); + else if (item instanceof Array) + item.forEach(addItem) + else + a.concat(item as WatCode); + } + } + + addItem(s); + addItem(bindings[i]); return a; - }, new WATCode()); + }, new WatCode()); } +wat.join = function ( + e: expr.Expr | value.Value, + s: string, + ...exprs: Array +): WatCode { + const ret = new WatCode(); -/* Moved to Type.getWasmTypename -export function watTypename(type : types.Type, name: string = ''): string { - if (type instanceof types.ClassType) - type = type.getBaseType(); - - if (type instanceof types.PrimitiveType) - return type.name; - - if (type instanceof types.ArrowType) - return `(func ${name} (param ${ - type.inputTypes.map(t => watTypename(t)).join(' ') - }) (result ${ - type.outputTypes.map(t => watTypename(t)).join(' ') - }))`; - - if (type instanceof types.TupleType) - return type.types.map(t => watTypename(t)).join(' '); + function addItem(item: WatCode | string | WatCode[]) { + if (item) { + if (typeof item === 'string') + ret.add(e, item as string); + else if (item instanceof Array) + item.forEach(addItem) + else + ret.concat(item as WatCode); + } + } - if (type instanceof types.UnionType) - throw new Error("cannot make wat typename for union type"); + // Join algo + addItem(exprs[0]); + for (let i = 1; i < exprs.length; i++) { + addItem(s); + addItem(exprs[i]); + } - // For unit type no typename - return ''; -} -*/ \ No newline at end of file + return ret; +}; \ No newline at end of file diff --git a/planning/raytracing_demo/material.phs b/planning/raytracing_demo/material.phs index 90b8f35..c230678 100644 --- a/planning/raytracing_demo/material.phs +++ b/planning/raytracing_demo/material.phs @@ -1,4 +1,4 @@ -"lang" require use +"../../std/lang.phs" require use "./vec3.phs" require $Vec3 = ( Vec3.Vec3 # Ambient color diff --git a/planning/raytracing_demo/ray.phs b/planning/raytracing_demo/ray.phs index 4c0b632..a4c1812 100644 --- a/planning/raytracing_demo/ray.phs +++ b/planning/raytracing_demo/ray.phs @@ -1,4 +1,4 @@ -"lang" require use +"../../std/lang.phs" require use ( Vec3.Vec3 # origin diff --git a/planning/raytracing_demo/raytrace.phs b/planning/raytracing_demo/raytrace.phs index a0f86ed..4c296be 100644 --- a/planning/raytracing_demo/raytrace.phs +++ b/planning/raytracing_demo/raytrace.phs @@ -1,13 +1,12 @@ -"lang" require use -"math" require $math = +"../../std/lang.phs" require use +"../../std/math.phs" require $math = +"../../std/maybe.phs" require $Maybe = +"../../std/mem.phs" require $mem = # Overload because i cba to properly implement pow ((F64 F64): 1 ) (: ( F64 F64 ) ( F64 ) Arrow ( "js" "Math.pow" ) import @ ) $math.pow fun -"maybe" require $Maybe = -"mem" require $mem = - "./vec3.phs" require $Vec3 = "./sphere.phs" require $Sphere = "./ray.phs" require $Ray = @@ -21,7 +20,7 @@ width height 4 * static_region $image_ptr = ((Unit Vec3.Vec3 I32 I32): ( $color $x $y ) = - color 0.0 1.0 Vec3.clamp unpack ( $r $g $b ) = + color unpack ( $r $g $b ) = # Pack color into a single i32 r 255 * I32 cast 24 << diff --git a/planning/raytracing_demo/scene.phs b/planning/raytracing_demo/scene.phs index 7f3c923..dc67331 100644 --- a/planning/raytracing_demo/scene.phs +++ b/planning/raytracing_demo/scene.phs @@ -1 +1 @@ -"lang" require use \ No newline at end of file +"../../std/lang.phs" require use \ No newline at end of file diff --git a/planning/raytracing_demo/sphere.phs b/planning/raytracing_demo/sphere.phs index f280e1d..fcffd2f 100644 --- a/planning/raytracing_demo/sphere.phs +++ b/planning/raytracing_demo/sphere.phs @@ -1,4 +1,4 @@ -"lang" require use +"../../std/lang.phs" require use "./vec3.phs" require $Vec3 = "./material.phs" require $Material = @@ -7,7 +7,7 @@ ( Vec3.Vec3 # Center F64 # Radius Material.Material # Material -) class $Sphere = +) rec class $Sphere = ((Sphere): unpack ( $center $radius $material ) = center ) $center = diff --git a/planning/raytracing_demo/vec3.phs b/planning/raytracing_demo/vec3.phs index ff85ca4..22bb01c 100644 --- a/planning/raytracing_demo/vec3.phs +++ b/planning/raytracing_demo/vec3.phs @@ -1,5 +1,5 @@ -"lang" require use -"math" require $math = +"../../std/lang.phs" require use +"../../std/math.phs" require $math = # Namespace Vec3 diff --git a/planning/rec.phs b/planning/rec.phs new file mode 100644 index 0000000..7aa978b --- /dev/null +++ b/planning/rec.phs @@ -0,0 +1,17 @@ +"lang" require use +"math" require $math = + +( F64 F64 ) rec class rec $Vec = + +((Vec Vec): + unpack ( $x1 $y1 ) = + unpack ( $x2 $y2 ) = + x1 x2 * y1 y2 * + +) $dp = + +( F64 F64 F64 F64 ) (: + ( $x2 $y2 ) = + ( $x1 $y1 ) = + ( x1 y1 ) Vec make + ( x2 y2 ) Vec make dp +) "demo" export \ No newline at end of file diff --git a/tools/file.ts b/tools/file.ts index 6a4d426..c2a3747 100644 --- a/tools/file.ts +++ b/tools/file.ts @@ -74,7 +74,7 @@ export default async function compileFile( // Output assembly start = performance.now(); - const wast = await (ctx as Context).outWast({ folding, fast, optimize: optimize !== 0, }); + const wast = await (ctx as Context).outWast({ folding, fast, optimize: optimize !== 0, validate: true }); if (trackTime) console.log('compile:', performance.now() - start);