From 937ebea6a2f2e3c9a7702812755c0361154b3fbc Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Sat, 11 Jan 2020 11:23:02 +0300 Subject: [PATCH 1/2] src: pack struct by sorting fields --- src/compiler/header-builder.ts | 86 ++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/src/compiler/header-builder.ts b/src/compiler/header-builder.ts index 9f5bee7..5b886d0 100644 --- a/src/compiler/header-builder.ts +++ b/src/compiler/header-builder.ts @@ -15,6 +15,7 @@ export class HeaderBuilder { const DEFINE = options.headerGuard === undefined ? `INCLUDE_${PREFIX}_H_` : options.headerGuard; + // ifdef res += `#ifndef ${DEFINE}\n`; res += `#define ${DEFINE}\n`; res += '#ifdef __cplusplus\n'; @@ -25,51 +26,78 @@ export class HeaderBuilder { res += '#include \n'; res += '\n'; - // Structure - res += `typedef struct ${options.prefix}_s ${options.prefix}_t;\n`; - res += `struct ${options.prefix}_s {\n`; - res += ' int32_t _index;\n'; + // Structure fields + const fields: Array<{ name: string, size: number, type: string }> = []; + fields.push({ name: '_index', size: 4, type: 'int32_t' }); for (const [ index, field ] of options.spans.entries()) { - res += ` void* _span_pos${index};\n`; + fields.push({ name: `_span_pos${index}`, size: -1, type: 'void*' }); if (field.callbacks.length > 1) { - res += ` void* _span_cb${index};\n`; + fields.push({ name: `_span_cb${index}`, size: -1, type: 'void*' }); } } - res += ' int32_t error;\n'; - res += ' const char* reason;\n'; - res += ' const char* error_pos;\n'; - res += ' void* data;\n'; - res += ' void* _current;\n'; - - for (const prop of options.properties) { - let ty: string; - if (prop.ty === 'i8') { - ty = 'uint8_t'; - } else if (prop.ty === 'i16') { - ty = 'uint16_t'; - } else if (prop.ty === 'i32') { - ty = 'uint32_t'; - } else if (prop.ty === 'i64') { - ty = 'uint64_t'; - } else if (prop.ty === 'ptr') { - ty = 'void*'; + fields.push({ name: 'error', size: 4, type: 'int32_t' }); + fields.push({ name: 'reason', size: -1, type: 'const char*' }); + fields.push({ name: 'error_pos', size: -1, type: 'const char*' }); + fields.push({ name: 'data', size: -1, type: 'void*' }); + fields.push({ name: '_current', size: -1, type: 'void*' }); + + for (const { name, ty } of options.properties) { + if (ty === 'i8') { + fields.push({ name, size: 1, type: 'uint8_t' }); + } else if (ty === 'i16') { + fields.push({ name, size: 2, type: 'uint16_t' }); + } else if (ty === 'i32') { + fields.push({ name, size: 4, type: 'uint32_t' }); + } else if (ty === 'i64') { + fields.push({ name, size: 8, type: 'uint64_t' }); + } else if (ty === 'ptr') { + fields.push({ name, size: -1, type: 'void*' }); } else { - throw new Error( - `Unknown state property type: "${prop.ty}"`); + throw new Error(`Unknown state property type: "${ty}"`); } - res += ` ${ty} ${prop.name};\n`; } - res += '};\n'; + // Sort fields from bigger size to lower. + // Pointers are special, they should be after 8-bytes, but before 4-bytes. + // In this case align work for 32-bit and 64-bit. + fields.sort((a, b) => { + if (a.name === b.name) { + throw new Error(`Two fields with same name: "${a.name}"`); + } + + if (a.size === b.size) { + return a.name < b.name ? -1 : 1; + } + + if (a.size === -1) { + return b.size >= 8 ? 1 : -1; + } + + if (b.size === -1) { + return a.size >= 8 ? -1 : 1; + } + + return b.size - a.size; + }); + + // Structure + res += `typedef struct ${options.prefix}_s ${options.prefix}_t;\n`; + res += `struct ${options.prefix}_s {\n`; + for (const { name, type } of fields) { + res += ` ${type} ${name};\n`; + } + res += '};\n'; res += '\n'; + // Functions: init, execute res += `int ${options.prefix}_init(${options.prefix}_t* s);\n`; res += `int ${options.prefix}_execute(${options.prefix}_t* s, ` + 'const char* p, const char* endp);\n'; - res += '\n'; + + // endif res += '#ifdef __cplusplus\n'; res += '} /* extern "C" *\/\n'; res += '#endif\n'; From be8c4a3b711cb8ede0db9c7db00b79421f3caec4 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Mon, 20 Jan 2020 22:33:32 +0300 Subject: [PATCH 2/2] src: fix struct packing for bitcode --- src/compiler/header-builder.ts | 73 ++------------------- src/compiler/index.ts | 17 +++-- src/compiler/struct-state-fields-builder.ts | 70 ++++++++++++++++++++ src/implementation/bitcode/compilation.ts | 59 +++++++++-------- src/implementation/bitcode/index.ts | 7 +- 5 files changed, 121 insertions(+), 105 deletions(-) create mode 100644 src/compiler/struct-state-fields-builder.ts diff --git a/src/compiler/header-builder.ts b/src/compiler/header-builder.ts index 5b886d0..c14d3ed 100644 --- a/src/compiler/header-builder.ts +++ b/src/compiler/header-builder.ts @@ -1,19 +1,14 @@ import * as frontend from 'llparse-frontend'; -import source = frontend.source; - -export interface IHeaderBuilderOptions { - readonly prefix: string; - readonly headerGuard?: string; - readonly properties: ReadonlyArray; - readonly spans: ReadonlyArray; -} +import { IStructStateFields } from './struct-state-fields-builder'; export class HeaderBuilder { - public build(options: IHeaderBuilderOptions): string { + public build(headerGuard: string | undefined, + fields: IStructStateFields, + options: frontend.IFrontendResult): string { let res = ''; const PREFIX = options.prefix.toUpperCase().replace(/[^a-z]/gi, '_'); - const DEFINE = options.headerGuard === undefined ? - `INCLUDE_${PREFIX}_H_` : options.headerGuard; + const DEFINE = headerGuard === undefined ? + `INCLUDE_${PREFIX}_H_` : headerGuard; // ifdef res += `#ifndef ${DEFINE}\n`; @@ -26,62 +21,6 @@ export class HeaderBuilder { res += '#include \n'; res += '\n'; - // Structure fields - const fields: Array<{ name: string, size: number, type: string }> = []; - fields.push({ name: '_index', size: 4, type: 'int32_t' }); - - for (const [ index, field ] of options.spans.entries()) { - fields.push({ name: `_span_pos${index}`, size: -1, type: 'void*' }); - if (field.callbacks.length > 1) { - fields.push({ name: `_span_cb${index}`, size: -1, type: 'void*' }); - } - } - - fields.push({ name: 'error', size: 4, type: 'int32_t' }); - fields.push({ name: 'reason', size: -1, type: 'const char*' }); - fields.push({ name: 'error_pos', size: -1, type: 'const char*' }); - fields.push({ name: 'data', size: -1, type: 'void*' }); - fields.push({ name: '_current', size: -1, type: 'void*' }); - - for (const { name, ty } of options.properties) { - if (ty === 'i8') { - fields.push({ name, size: 1, type: 'uint8_t' }); - } else if (ty === 'i16') { - fields.push({ name, size: 2, type: 'uint16_t' }); - } else if (ty === 'i32') { - fields.push({ name, size: 4, type: 'uint32_t' }); - } else if (ty === 'i64') { - fields.push({ name, size: 8, type: 'uint64_t' }); - } else if (ty === 'ptr') { - fields.push({ name, size: -1, type: 'void*' }); - } else { - throw new Error(`Unknown state property type: "${ty}"`); - } - } - - // Sort fields from bigger size to lower. - // Pointers are special, they should be after 8-bytes, but before 4-bytes. - // In this case align work for 32-bit and 64-bit. - fields.sort((a, b) => { - if (a.name === b.name) { - throw new Error(`Two fields with same name: "${a.name}"`); - } - - if (a.size === b.size) { - return a.name < b.name ? -1 : 1; - } - - if (a.size === -1) { - return b.size >= 8 ? 1 : -1; - } - - if (b.size === -1) { - return a.size >= 8 ? -1 : 1; - } - - return b.size - a.size; - }); - // Structure res += `typedef struct ${options.prefix}_s ${options.prefix}_t;\n`; res += `struct ${options.prefix}_s {\n`; diff --git a/src/compiler/index.ts b/src/compiler/index.ts index 7206ce4..866defb 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -7,6 +7,7 @@ import * as bitcodeImpl from '../implementation/bitcode'; import * as cImpl from '../implementation/c'; import * as jsImpl from '../implementation/js'; import { HeaderBuilder } from './header-builder'; +import { StructStateFieldsBuilder } from './struct-state-fields-builder'; const debug = debugAPI('llparse:compiler'); @@ -117,15 +118,13 @@ export class Compiler { this.options.frontend); const info = f.compile(root, properties); - debug('Building header'); - const hb = new HeaderBuilder(); + debug('Building fields for state struct'); + const fields = new StructStateFieldsBuilder().build(info); - const header = hb.build({ - headerGuard: this.options.headerGuard, - prefix: this.prefix, - properties, - spans: info.spans, - }); + debug('Building header'); + const header = new HeaderBuilder().build(this.options.headerGuard, + fields, + info); const result: IWritableCompilerResult = { bitcode: undefined, @@ -134,7 +133,7 @@ export class Compiler { debug('Building bitcode'); if (bitcode) { - result.bitcode = bitcode.compile(info); + result.bitcode = bitcode.compile(fields, info); } debug('Building C'); diff --git a/src/compiler/struct-state-fields-builder.ts b/src/compiler/struct-state-fields-builder.ts new file mode 100644 index 0000000..6acebc3 --- /dev/null +++ b/src/compiler/struct-state-fields-builder.ts @@ -0,0 +1,70 @@ +import * as frontend from 'llparse-frontend'; + +interface IStructStateField { + readonly name: string; + readonly size: number; + readonly type: string; +} + +export type IStructStateFields = IStructStateField[]; + +export class StructStateFieldsBuilder { + public build(options: frontend.IFrontendResult): IStructStateFields { + const fields: IStructStateFields = []; + fields.push({ name: '_index', size: 4, type: 'int32_t' }); + + for (const [ index, field ] of options.spans.entries()) { + fields.push({ name: `_span_pos${index}`, size: -1, type: 'void*' }); + if (field.callbacks.length > 1) { + fields.push({ name: `_span_cb${index}`, size: -1, type: 'void*' }); + } + } + + fields.push({ name: 'error', size: 4, type: 'int32_t' }); + fields.push({ name: 'reason', size: -1, type: 'const char*' }); + fields.push({ name: 'error_pos', size: -1, type: 'const char*' }); + fields.push({ name: 'data', size: -1, type: 'void*' }); + fields.push({ name: '_current', size: -1, type: 'void*' }); + + for (const { name, ty } of options.properties) { + if (ty === 'i8') { + fields.push({ name, size: 1, type: 'uint8_t' }); + } else if (ty === 'i16') { + fields.push({ name, size: 2, type: 'uint16_t' }); + } else if (ty === 'i32') { + fields.push({ name, size: 4, type: 'uint32_t' }); + } else if (ty === 'i64') { + fields.push({ name, size: 8, type: 'uint64_t' }); + } else if (ty === 'ptr') { + fields.push({ name, size: -1, type: 'void*' }); + } else { + throw new Error(`Unknown state property type: "${ty}"`); + } + } + + // Sort fields from bigger size to lower. + // Pointers are special, they should be after 8-bytes, but before 4-bytes. + // In this case align work for 32-bit and 64-bit. + fields.sort((a, b) => { + if (a.name === b.name) { + throw new Error(`Two fields with same name: "${a.name}"`); + } + + if (a.size === b.size) { + return a.name < b.name ? -1 : 1; + } + + if (a.size === -1) { + return b.size >= 8 ? 1 : -1; + } + + if (b.size === -1) { + return a.size >= 8 ? -1 : 1; + } + + return b.size - a.size; + }); + + return fields; + } +} diff --git a/src/implementation/bitcode/compilation.ts b/src/implementation/bitcode/compilation.ts index 45d1a80..407685b 100644 --- a/src/implementation/bitcode/compilation.ts +++ b/src/implementation/bitcode/compilation.ts @@ -7,6 +7,7 @@ import { import { Buffer } from 'buffer'; import * as frontend from 'llparse-frontend'; +import { IStructStateFields } from '../../compiler/struct-state-fields-builder' import * as constants from './constants'; import { MatchSequence } from './helpers/match-sequence'; import { Code } from './code'; @@ -83,8 +84,8 @@ export class Compilation { private debugMethod: IRDeclaration | undefined = undefined; constructor(public readonly prefix: string, + fields: IStructStateFields, private readonly properties: ReadonlyArray, - spans: ReadonlyArray, public readonly options: ICompilationOptions) { this.ir = this.bitcode.createBuilder(); this.invariantGroup = this.ir.metadata([ @@ -120,36 +121,42 @@ export class Compilation { ]), }; - // Put most used fields first - this.state.addField(constants.TYPE_INDEX, constants.STATE_INDEX); + const propTypeMap: { [key: string]: IRType } = { + uint8_t: constants.I8, + uint16_t: constants.I16, + uint32_t: constants.I32, + uint64_t: constants.I64, + 'void*': constants.PTR, + }; + + const typeMap: { [key: string]: IRType } = { + [constants.STATE_INDEX]: constants.TYPE_INDEX, + [constants.STATE_ERROR]: constants.TYPE_ERROR, + [constants.STATE_REASON]: constants.TYPE_REASON, + [constants.STATE_ERROR_POS]: constants.TYPE_ERROR_POS, + [constants.STATE_DATA]: constants.TYPE_DATA, + [constants.STATE_CURRENT]: this.signature.node.ptr(), + }; - spans.forEach((span) => { - this.state.addField(constants.TYPE_SPAN_POS, - constants.STATE_SPAN_POS + span.index); - if (span.callbacks.length > 1) { - this.state.addField(this.signature.callback.span.ptr(), - constants.STATE_SPAN_CB + span.index); + for (const { name, type } of fields) { + const isProp = properties.some((p) => p.name === name); + if (isProp) { + this.state.addField(propTypeMap[type], name); + continue; } - }); - this.state.addField(constants.TYPE_ERROR, constants.STATE_ERROR); - this.state.addField(constants.TYPE_REASON, constants.STATE_REASON); - this.state.addField(constants.TYPE_ERROR_POS, constants.STATE_ERROR_POS); - this.state.addField(constants.TYPE_DATA, constants.STATE_DATA); - this.state.addField(this.signature.node.ptr(), constants.STATE_CURRENT); - - for (const property of properties) { - let ty: IRType; - switch (property.ty) { - case 'i8': ty = constants.I8; break; - case 'i16': ty = constants.I16; break; - case 'i32': ty = constants.I32; break; - case 'i64': ty = constants.I64; break; - case 'ptr': ty = constants.PTR; break; - default: throw new Error(`Unsupported user type: "${property.ty}"`); + let ty = typeMap[name]; + if (!ty) { + if (name.startsWith(constants.STATE_SPAN_POS)) { + ty = constants.TYPE_SPAN_POS; + } else if (name.startsWith(constants.STATE_SPAN_CB)) { + ty = this.signature.callback.span.ptr(); + } else { + throw new Error(`Unknow field: "${name}"`); + } } - this.state.addField(ty, property.name); + this.state.addField(ty, name); } this.state.finalize(); diff --git a/src/implementation/bitcode/index.ts b/src/implementation/bitcode/index.ts index 40ea3ad..1ba8423 100644 --- a/src/implementation/bitcode/index.ts +++ b/src/implementation/bitcode/index.ts @@ -2,6 +2,7 @@ import { Buffer } from 'buffer'; import * as debugAPI from 'debug'; import * as frontend from 'llparse-frontend'; +import { IStructStateFields } from '../../compiler/struct-state-fields-builder' import { Compilation } from './compilation'; import { CONTAINER_KEY } from './constants'; import code from './code'; @@ -23,10 +24,10 @@ export class BitcodeCompiler { container.add(CONTAINER_KEY, { code, node, transform }); } - public compile(info: frontend.IFrontendResult): Buffer { + public compile(fields: IStructStateFields, info: frontend.IFrontendResult): Buffer { // Compile to bitcode - const compilation = new Compilation(info.prefix, info.properties, - info.spans, this.options); + const compilation = new Compilation(info.prefix, fields, info.properties, + this.options); debug('building root'); const root = info.root as frontend.ContainerWrap;