#!/usr/bin/env python2 # # Generate initialization data for built-in strings and objects. # # Supports two different initialization approaches: # # 1. Bit-packed format for unpacking strings and objects during # heap or thread init into RAM-based structures. This is the # default behavior. # # 2. Embedding strings and/or objects into a read-only data section # at compile time. This is useful for low memory targets to reduce # memory usage. Objects in data section will be immutable. # # Both of these have practical complications like endianness differences, # pointer compression variants, object property table layout variants, # and so on. Multiple #if defined()'d initializer sections are emitted # to cover all supported alternatives. # import logging import sys logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(name)-21s %(levelname)-7s %(message)s') logger = logging.getLogger('genbuiltins.py') logger.setLevel(logging.INFO) import os import re import traceback import json import yaml import math import struct import optparse import copy import logging import dukutil # Fixed seed for ROM strings, must match src-input/duk_heap_alloc.c. DUK__FIXED_HASH_SEED = 0xabcd1234 # Base value for compressed ROM pointers, used range is [ROMPTR_FIRST,0xffff]. # Must match DUK_USE_ROM_PTRCOMP_FIRST (generated header checks). ROMPTR_FIRST = 0xf800 # 2048 should be enough; now around ~1000 used # ROM string table size ROMSTR_LOOKUP_SIZE = 256 # # Miscellaneous helpers # # Convert Unicode to bytes, identifying Unicode U+0000 to U+00FF as bytes. # This representation is used in YAML metadata and allows invalid UTF-8 to # be represented exactly (which is necessary). def unicode_to_bytes(x): if isinstance(x, str): return x tmp = '' for c in x: if ord(c) > 0xff: raise Exception('invalid codepoint: %r' % x) tmp += chr(ord(c)) assert(isinstance(tmp, str)) return tmp # Convert bytes to Unicode, identifying bytes as U+0000 to U+00FF. def bytes_to_unicode(x): if isinstance(x, unicode): return x tmp = u'' for c in x: tmp += unichr(ord(c)) assert(isinstance(tmp, unicode)) return tmp # Convert all strings in an object to bytes recursively. Useful for # normalizing all strings in a YAML document. def recursive_strings_to_bytes(doc): def f(x): if isinstance(x, unicode): return unicode_to_bytes(x) if isinstance(x, dict): res = {} for k in x.keys(): res[f(k)] = f(x[k]) return res if isinstance(x, list): res = [] for e in x: res.append(f(e)) return res return x return f(doc) # Convert all strings in an object to from bytes to Unicode recursively. # Useful for writing back JSON/YAML dumps. def recursive_bytes_to_strings(doc): def f(x): if isinstance(x, str): return bytes_to_unicode(x) if isinstance(x, dict): res = {} for k in x.keys(): res[f(k)] = f(x[k]) return res if isinstance(x, list): res = [] for e in x: res.append(f(e)) return res return x return f(doc) # Check if string is an "array index" in ECMAScript terms. def string_is_arridx(v): is_arridx = False try: ival = int(v) if ival >= 0 and ival <= 0xfffffffe and ('%d' % ival == v): is_arridx = True except ValueError: pass return is_arridx # # Metadata loading, merging, and other preprocessing # # Final metadata object contains merged and normalized objects and strings. # Keys added include (see more below): # # strings_stridx: string objects which have a stridx, matches stridx index order # objects_bidx: objects which have a bidx, matches bidx index order # objects_ram_toplevel: objects which are top level for RAM init # # Various helper keys are also added, containing auxiliary object/string # lists, lookup maps, etc. See code below for details of these. # def metadata_lookup_object(meta, obj_id): return meta['_objid_to_object'][obj_id] def metadata_lookup_object_and_index(meta, obj_id): for i,t in enumerate(meta['objects']): if t['id'] == obj_id: return t, i return None, None def metadata_lookup_property(obj, key): for p in obj['properties']: if p['key'] == key: return p return None def metadata_lookup_property_and_index(obj, key): for i,t in enumerate(obj['properties']): if t['key'] == key: return t, i return None, None # Remove disabled objects and properties. def metadata_remove_disabled(meta, active_opts): objlist = [] count_disabled_object = 0 count_notneeded_object = 0 count_disabled_property = 0 count_notneeded_property = 0 def present_if_check(v): pi = v.get('present_if', None) if pi is None: return True if isinstance(pi, (str, unicode)): pi = [ pi ] if not isinstance(pi, list): raise Exception('invalid present_if syntax: %r' % pi) # Present if all listed options are true or unknown. # Absent if any option is known to be false. for opt in pi: if active_opts.get(opt, None) == False: return False return True for o in meta['objects']: if o.get('disable', False): logger.debug('Remove disabled object: %s' % o['id']) count_disabled_object += 1 elif not present_if_check(o): logger.debug('Removed object not needed in active configuration: %s' % o['id']) count_notneeded_object += 1 else: objlist.append(o) props = [] for p in o['properties']: if p.get('disable', False): logger.debug('Remove disabled property: %s, object: %s' % (p['key'], o['id'])) count_disabled_property += 1 elif not present_if_check(p): logger.debug('Removed property not needed in active configuration: %s, object: %s' % (p['key'], o['id'])) count_notneeded_property += 1 else: props.append(p) o['properties'] = props meta['objects'] = objlist if count_disabled_object + count_notneeded_object + count_disabled_property + count_notneeded_property > 0: logger.info('Removed %d objects (%d disabled, %d not needed by config), %d properties (%d disabled, %d not needed by config)' % (count_disabled_object + count_notneeded_object, count_disabled_object, count_notneeded_object, count_disabled_property + count_notneeded_property, count_disabled_property, count_notneeded_property)) # Delete dangling references to removed/missing objects. def metadata_delete_dangling_references_to_object(meta, obj_id): for o in meta['objects']: new_p = [] for p in o['properties']: v = p['value'] ptype = None if isinstance(v, dict): ptype = p['value']['type'] delprop = False if ptype == 'object' and v['id'] == obj_id: delprop = True if ptype == 'accessor' and v.get('getter_id') == obj_id: p['getter_id'] = None if ptype == 'accessor' and v.get('setter_id') == obj_id: p['setter_id'] = None # XXX: Should empty accessor (= no getter, no setter) be deleted? # If so, beware of shorthand. if delprop: logger.debug('Deleted property %s of object %s, points to deleted object %s' % \ (p['key'], o['id'], obj_id)) else: new_p.append(p) o['properties'] = new_p # Merge a user YAML file into current metadata. def metadata_merge_user_objects(meta, user_meta): if user_meta.has_key('add_objects'): raise Exception('"add_objects" removed, use "objects" with "add: True"') if user_meta.has_key('replace_objects'): raise Exception('"replace_objects" removed, use "objects" with "replace: True"') if user_meta.has_key('modify_objects'): raise Exception('"modify_objects" removed, use "objects" with "modify: True"') for o in user_meta.get('objects', []): if o.get('disable', False): logger.debug('Skip disabled object: %s' % o['id']) continue targ, targ_idx = metadata_lookup_object_and_index(meta, o['id']) if o.get('delete', False): logger.debug('Delete object: %s' % targ['id']) if targ is None: raise Exception('Cannot delete object %s which doesn\'t exist' % o['id']) meta['objects'].pop(targ_idx) metadata_delete_dangling_references_to_object(meta, targ['id']) continue if o.get('replace', False): logger.debug('Replace object %s' % o['id']) if targ is None: logger.warning('object to be replaced doesn\'t exist, append new object') meta['objects'].append(o) else: meta['objects'][targ_idx] = o continue if o.get('add', False) or not o.get('modify', False): # 'add' is the default logger.debug('Add object %s' % o['id']) if targ is not None: raise Exception('Cannot add object %s which already exists' % o['id']) meta['objects'].append(o) continue assert(o.get('modify', False)) # modify handling if targ is None: raise Exception('Cannot modify object %s which doesn\'t exist' % o['id']) for k in sorted(o.keys()): # Merge top level keys by copying over, except 'properties' if k == 'properties': continue targ[k] = o[k] for p in o.get('properties', []): if p.get('disable', False): logger.debug('Skip disabled property: %s' % p['key']) continue prop = None prop_idx = None prop, prop_idx = metadata_lookup_property_and_index(targ, p['key']) if prop is not None: if p.get('delete', False): logger.debug('Delete property %s of %s' % (p['key'], o['id'])) targ['properties'].pop(prop_idx) else: logger.debug('Replace property %s of %s' % (p['key'], o['id'])) targ['properties'][prop_idx] = p else: if p.get('delete', False): logger.debug('Deleting property %s of %s: doesn\'t exist, nop' % (p['key'], o['id'])) else: logger.debug('Add property %s of %s' % (p['key'], o['id'])) targ['properties'].append(p) # Replace 'symbol' keys and values with encoded strings. def format_symbol(sym): #print(repr(sym)) assert(isinstance(sym, dict)) assert(sym.get('type', None) == 'symbol') variant = sym['variant'] if variant == 'global': return '\x80' + sym['string'] elif variant == 'wellknown': # Well known symbols use an empty suffix which never occurs for # runtime local symbols. return '\x81' + sym['string'] + '\xff' elif variant == 'userhidden': return '\xff' + sym['string'] elif variant == 'hidden': # hidden == Duktape hidden Symbol return '\x82' + sym['string'] raise Exception('invalid symbol variant %r' % variant) def metadata_normalize_symbol_strings(meta): for o in meta['strings']: if isinstance(o['str'], dict) and o['str'].get('type') == 'symbol': o['str'] = format_symbol(o['str']) #print('normalized symbol as string list element: %r', o) for o in meta['objects']: for p in o['properties']: if isinstance(p['key'], dict) and p['key'].get('type') == 'symbol': p['key'] = format_symbol(p['key']) #print('normalized symbol as property key: %r', p) if isinstance(p['value'], dict) and p['value'].get('type') == 'symbol': p['value'] = format_symbol(p['value']) #print('normalized symbol as property value: %r', p) # Normalize nargs for top level functions by defaulting 'nargs' from 'length'. def metadata_normalize_nargs_length(meta): # Default 'nargs' from 'length' for top level function objects. for o in meta['objects']: if o.has_key('nargs'): continue if not o.get('callable', False): continue for p in o['properties']: if p['key'] != 'length': continue logger.debug('Default nargs for top level: %r' % p) assert(isinstance(p['value'], int)) o['nargs'] = p['value'] break assert(o.has_key('nargs')) # Default 'nargs' from 'length' for function property shorthand. for o in meta['objects']: for p in o['properties']: if not (isinstance(p['value'], dict) and p['value']['type'] == 'function'): continue pval = p['value'] if not pval.has_key('length'): logger.debug('Default length for function shorthand: %r' % p) pval['length'] = 0 if not pval.has_key('nargs'): logger.debug('Default nargs for function shorthand: %r' % p) pval['nargs'] = pval['length'] # Prepare a list of built-in objects which need a runtime 'bidx'. def metadata_prepare_objects_bidx(meta): objlist = meta['objects'] meta['objects'] = [] meta['objects_bidx'] = [] # Objects have a 'bidx: true' if they need a DUK_BIDX_xxx constant # and need to be present in thr->builtins[]. The list is already # stripped of built-in objects which are not needed based on config. # Ideally we'd scan the actually needed indices from the source # but since some usage is inside #if defined()s that's not trivial. for obj in objlist: if obj.get('bidx', False): obj['bidx_used'] = True meta['objects'].append(obj) meta['objects_bidx'].append(obj) # Append remaining objects. for obj in objlist: if obj.get('bidx_used', False): # Already in meta['objects']. pass else: meta['objects'].append(obj) # Normalize metadata property shorthand. For example, if a property value # is a shorthand function, create a function object and change the property # to point to that function object. def metadata_normalize_shorthand(meta): # Gather objects through the top level built-ins list. objs = [] subobjs = [] def getSubObject(): obj = {} obj['id'] = 'subobj_%d' % len(subobjs) # synthetic ID obj['properties'] = [] obj['auto_generated'] = True # mark as autogenerated (just FYI) subobjs.append(obj) return obj def decodeFunctionShorthand(funprop): # Convert the built-in function property "shorthand" into an actual # object for ROM built-ins. assert(funprop['value']['type'] == 'function') val = funprop['value'] obj = getSubObject() props = obj['properties'] obj['native'] = val['native'] obj['nargs'] = val.get('nargs', val['length']) obj['varargs'] = val.get('varargs', False) obj['magic'] = val.get('magic', 0) obj['internal_prototype'] = 'bi_function_prototype' obj['class'] = 'Function' obj['callable'] = val.get('callable', True) obj['constructable'] = val.get('constructable', False) obj['special_call'] = val.get('special_call', False) fun_name = val.get('name', funprop['key']) props.append({ 'key': 'length', 'value': val['length'], 'attributes': 'c' }) # Configurable in ES2015 props.append({ 'key': 'name', 'value': fun_name, 'attributes': 'c' }) # Configurable in ES2015 return obj def addAccessor(funprop, magic, nargs, length, name, native_func): assert(funprop['value']['type'] == 'accessor') obj = getSubObject() props = obj['properties'] obj['native'] = native_func obj['nargs'] = nargs obj['varargs'] = False obj['magic'] = magic obj['internal_prototype'] = 'bi_function_prototype' obj['class'] = 'Function' obj['callable'] = val.get('callable', True) obj['constructable'] = val.get('constructable', False) assert(obj.get('special_call', False) == False) # Shorthand accessors are minimal and have no .length or .name # right now. Use longhand if these matter. #props.append({ 'key': 'length', 'value': length, 'attributes': 'c' }) #props.append({ 'key': 'name', 'value': name, 'attributes': 'c' }) return obj def decodeGetterShorthand(key, funprop): assert(funprop['value']['type'] == 'accessor') val = funprop['value'] if not val.has_key('getter'): return None return addAccessor(funprop, val['getter_magic'], val['getter_nargs'], val.get('getter_length', 0), key, val['getter']) def decodeSetterShorthand(key, funprop): assert(funprop['value']['type'] == 'accessor') val = funprop['value'] if not val.has_key('setter'): return None return addAccessor(funprop, val['setter_magic'], val['setter_nargs'], val.get('setter_length', 0), key, val['setter']) def decodeStructuredValue(val): logger.debug('Decode structured value: %r' % val) if isinstance(val, (int, long, float, str)): return val # as is elif isinstance(val, (dict)): # Object: decode recursively obj = decodeStructuredObject(val) return { 'type': 'object', 'id': obj['id'] } elif isinstance(val, (list)): raise Exception('structured shorthand does not yet support array literals') else: raise Exception('unsupported value in structured shorthand: %r' % v) def decodeStructuredObject(val): # XXX: We'd like to preserve dict order from YAML source but # Python doesn't do that. Use sorted order to make the result # deterministic. User can always use longhand for exact # property control. logger.debug('Decode structured object: %r' % val) obj = getSubObject() obj['class'] = 'Object' obj['internal_prototype'] = 'bi_object_prototype' props = obj['properties'] keys = sorted(val.keys()) for k in keys: logger.debug('Decode property %s' % k) prop = { 'key': k, 'value': decodeStructuredValue(val[k]), 'attributes': 'wec' } props.append(prop) return obj def decodeStructuredShorthand(structprop): assert(structprop['value']['type'] == 'structured') val = structprop['value']['value'] return decodeStructuredValue(val) def clonePropShared(prop): res = {} for k in [ 'key', 'attributes', 'auto_lightfunc' ]: if prop.has_key(k): res[k] = prop[k] return res for idx,obj in enumerate(meta['objects']): props = [] repl_props = [] for val in obj['properties']: # Date.prototype.toGMTString must point to the same Function object # as Date.prototype.toUTCString, so special case hack it here. if obj['id'] == 'bi_date_prototype' and val['key'] == 'toGMTString': logger.debug('Skip Date.prototype.toGMTString') continue if isinstance(val['value'], dict) and val['value']['type'] == 'function': # Function shorthand. subfun = decodeFunctionShorthand(val) prop = clonePropShared(val) prop['value'] = { 'type': 'object', 'id': subfun['id'] } repl_props.append(prop) elif isinstance(val['value'], dict) and val['value']['type'] == 'accessor' and \ (val['value'].has_key('getter') or val['value'].has_key('setter')): # Accessor normal and shorthand forms both use the type 'accessor', # but are differentiated by properties. sub_getter = decodeGetterShorthand(val['key'], val) sub_setter = decodeSetterShorthand(val['key'], val) prop = clonePropShared(val) prop['value'] = { 'type': 'accessor' } if sub_getter is not None: prop['value']['getter_id'] = sub_getter['id'] if sub_setter is not None: prop['value']['setter_id'] = sub_setter['id'] assert('a' in prop['attributes']) # If missing, weird things happen runtime logger.debug('Expand accessor shorthand: %r -> %r' % (val, prop)) repl_props.append(prop) elif isinstance(val['value'], dict) and val['value']['type'] == 'structured': # Structured shorthand. subval = decodeStructuredShorthand(val) prop = clonePropShared(val) prop['value'] = subval repl_props.append(prop) logger.debug('Decoded structured shorthand for object %s, property %s' % (obj['id'], val['key'])) elif isinstance(val['value'], dict) and val['value']['type'] == 'buffer': # Duktape buffer type not yet supported. raise Exception('Buffer type not yet supported for builtins: %r' % val) elif isinstance(val['value'], dict) and val['value']['type'] == 'pointer': # Duktape pointer type not yet supported. raise Exception('Pointer type not yet supported for builtins: %r' % val) else: # Property already in normalized form. repl_props.append(val) if obj['id'] == 'bi_date_prototype' and val['key'] == 'toUTCString': logger.debug('Clone Date.prototype.toUTCString to Date.prototype.toGMTString') prop2 = copy.deepcopy(repl_props[-1]) prop2['key'] = 'toGMTString' repl_props.append(prop2) # Replace properties with a variant where function properties # point to built-ins rather than using an inline syntax. obj['properties'] = repl_props len_before = len(meta['objects']) meta['objects'] += subobjs len_after = len(meta['objects']) logger.debug('Normalized metadata shorthand, %d objects -> %d final objects' % (len_before, len_after)) # Normalize property attribute order, default attributes, etc. def metadata_normalize_property_attributes(meta): for o in meta['objects']: for p in o['properties']: orig_attrs = p.get('attributes', None) is_accessor = (isinstance(p['value'], dict) and p['value']['type'] == 'accessor') # If missing, set default attributes. attrs = orig_attrs if attrs is None: if is_accessor: attrs = 'ca' # accessor default is configurable else: attrs = 'wc' # default is writable, configurable logger.debug('Defaulted attributes of %s/%s to %s' % (o['id'], p['key'], attrs)) # Decode flags to normalize their order in the end. writable = 'w' in attrs enumerable = 'e' in attrs configurable = 'c' in attrs accessor = 'a' in attrs # Force 'accessor' attribute for accessors. if is_accessor and not accessor: logger.debug('Property %s is accessor but has no "a" attribute, add attribute' % p['key']) accessor = True # Normalize order and write back. attrs = '' if writable: attrs += 'w' if enumerable: attrs += 'e' if configurable: attrs += 'c' if accessor: attrs += 'a' p['attributes'] = attrs if orig_attrs != attrs: logger.debug('Updated attributes of %s/%s from %r to %r' % (o['id'], p['key'], orig_attrs, attrs)) pass # Normalize ROM property attributes. def metadata_normalize_rom_property_attributes(meta): for o in meta['objects']: for p in o['properties']: # ROM properties must not be configurable (runtime code # depends on this). Writability is kept so that instance # objects can override parent properties. p['attributes'] = p['attributes'].replace('c', '') # Add a 'name' property for all top level functions; expected by RAM # initialization code. def metadata_normalize_ram_function_names(meta): num_added = 0 for o in meta['objects']: if not o.get('callable', False): continue name_prop = None for p in o['properties']: if p['key'] == 'name': name_prop = p break if name_prop is None: num_added += 1 logger.debug('Adding missing "name" property for function %s' % o['id']) o['properties'].append({ 'key': 'name', 'value': '', 'attributes': 'c' }) if num_added > 0: logger.debug('Added missing "name" property for %d functions' % num_added) # Add a built-in objects list for RAM initialization. def metadata_add_ram_filtered_object_list(meta): # For RAM init data to support user objects, we need to prepare a # filtered top level object list, containing only those objects which # need a value stack index during duk_hthread_builtins.c init process. # # Objects in meta['objects'] which are covered by inline property # notation in the init data (this includes e.g. member functions like # Math.cos) must not be present. objlist = [] for o in meta['objects']: keep = o.get('bidx_used', False) if o.has_key('native') and not o.has_key('bidx'): # Handled inline by run-time init code pass else: # Top level object keep = True if keep: objlist.append(o) logger.debug('Filtered RAM object list: %d objects with bidx, %d total top level objects' % \ (len(meta['objects_bidx']), len(objlist))) meta['objects_ram_toplevel'] = objlist # Add missing strings into strings metadata. For example, if an object # property key is not part of the strings list, append it there. This # is critical for ROM builtins because all property keys etc must also # be in ROM. def metadata_normalize_missing_strings(meta, user_meta): # We just need plain strings here. strs_have = {} for s in meta['strings']: strs_have[s['str']] = True # For ROM builtins all the strings must be in the strings list, # so scan objects for any strings not explicitly listed in metadata. for idx, obj in enumerate(meta['objects']): for prop in obj['properties']: key = prop['key'] if not strs_have.get(key): logger.debug('Add missing string: %r' % key) meta['strings'].append({ 'str': key, '_auto_add_ref': True }) strs_have[key] = True if prop.has_key('value') and isinstance(prop['value'], (str, unicode)): val = unicode_to_bytes(prop['value']) # should already be, just in case if not strs_have.get(val): logger.debug('Add missing string: %r' % val) meta['strings'].append({ 'str': val, '_auto_add_ref': True }) strs_have[val] = True # Force user strings to be in ROM data. for s in user_meta.get('add_forced_strings', []): if not strs_have.get(s['str']): logger.debug('Add user string: %r' % s['str']) s['_auto_add_user'] = True meta['strings'].append(s) # Convert built-in function properties into lightfuncs where applicable. def metadata_convert_lightfuncs(meta): num_converted = 0 num_skipped = 0 for o in meta['objects']: for p in o['properties']: v = p['value'] ptype = None if isinstance(v, dict): ptype = p['value']['type'] if ptype != 'object': continue targ, targ_idx = metadata_lookup_object_and_index(meta, p['value']['id']) reasons = [] if not targ.get('callable', False): reasons.append('not-callable') #if targ.get('constructable', False): # reasons.append('constructable') lf_len = 0 for p2 in targ['properties']: # Don't convert if function has more properties than # we're willing to sacrifice. logger.debug(' - Check %r . %s' % (o.get('id', None), p2['key'])) if p2['key'] == 'length' and isinstance(p2['value'], (int, long)): lf_len = p2['value'] if p2['key'] not in [ 'length', 'name' ]: reasons.append('nonallowed-property') if not p.get('auto_lightfunc', True): logger.debug('Automatic lightfunc conversion rejected for key %s, explicitly requested in metadata' % p['key']) reasons.append('no-auto-lightfunc') # lf_len comes from actual property table (after normalization) if targ.has_key('magic'): try: # Magic values which resolve to 'bidx' indices cannot # be resolved here yet, because the bidx map is not # yet ready. If so, reject the lightfunc conversion # for now. In practice this doesn't matter. lf_magic = resolve_magic(targ.get('magic'), {}) # empty map is a "fake" bidx map logger.debug('resolved magic ok -> %r' % lf_magic) except Exception, e: logger.debug('Failed to resolve magic for %r: %r' % (p['key'], e)) reasons.append('magic-resolve-failed') lf_magic = 0xffffffff # dummy, will be out of bounds else: lf_magic = 0 if targ.get('varargs', True): lf_nargs = None lf_varargs = True else: lf_nargs = targ['nargs'] lf_varargs = False if lf_len < 0 or lf_len > 15: logger.debug('lf_len out of bounds: %r' % lf_len) reasons.append('len-bounds') if lf_magic < -0x80 or lf_magic > 0x7f: logger.debug('lf_magic out of bounds: %r' % lf_magic) reasons.append('magic-bounds') if not lf_varargs and (lf_nargs < 0 or lf_nargs > 14): logger.debug('lf_nargs out of bounds: %r' % lf_nargs) reasons.append('nargs-bounds') if len(reasons) > 0: logger.debug('Don\'t convert to lightfunc: %r %r (%r): %r' % (o.get('id', None), p.get('key', None), p['value']['id'], reasons)) num_skipped += 1 continue p_id = p['value']['id'] p['value'] = { 'type': 'lightfunc', 'native': targ['native'], 'length': lf_len, 'magic': lf_magic, 'nargs': lf_nargs, 'varargs': lf_varargs } logger.debug(' - Convert to lightfunc: %r %r (%r) -> %r' % (o.get('id', None), p.get('key', None), p_id, p['value'])) num_converted += 1 logger.debug('Converted %d built-in function properties to lightfuncs, %d skipped as non-eligible' % (num_converted, num_skipped)) # Detect objects not reachable from any object with a 'bidx'. This is usually # a user error because such objects can't be reached at runtime so they're # useless in RAM or ROM init data. def metadata_remove_orphan_objects(meta): reachable = {} for o in meta['objects']: if o.get('bidx_used', False): reachable[o['id']] = True while True: reachable_count = len(reachable.keys()) def _markId(obj_id): if obj_id is None: return reachable[obj_id] = True for o in meta['objects']: if not reachable.has_key(o['id']): continue _markId(o.get('internal_prototype', None)) for p in o['properties']: # Shorthand has been normalized so no need # to support it here. v = p['value'] ptype = None if isinstance(v, dict): ptype = p['value']['type'] if ptype == 'object': _markId(v['id']) if ptype == 'accessor': _markId(v.get('getter_id')) _markId(v.get('setter_id')) logger.debug('Mark reachable: reachable count initially %d, now %d' % \ (reachable_count, len(reachable.keys()))) if reachable_count == len(reachable.keys()): break num_deleted = 0 deleted = True while deleted: deleted = False for i,o in enumerate(meta['objects']): if not reachable.has_key(o['id']): logger.debug('object %s not reachable, dropping' % o['id']) meta['objects'].pop(i) deleted = True num_deleted += 1 break if num_deleted > 0: logger.debug('Deleted %d unreachable objects' % num_deleted) # Add C define names for builtin strings. These defines are added to all # strings, even when they won't get a stridx because the define names are # used to autodetect referenced strings. def metadata_add_string_define_names(strlist, special_defs): for s in strlist: v = s['str'] if special_defs.has_key(v): s['define'] = 'DUK_STRIDX_' + special_defs[v] continue if len(v) >= 1 and v[0] == '\x82': pfx = 'DUK_STRIDX_INT_' v = v[1:] elif len(v) >= 1 and v[0] == '\x81' and v[-1] == '\xff': pfx = 'DUK_STRIDX_WELLKNOWN_' v = v[1:-1] else: pfx = 'DUK_STRIDX_' t = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', v) # add underscores: aB -> a_B t = re.sub(r'\.', '_', t) # replace . with _, e.g. Symbol.iterator s['define'] = pfx + t.upper() logger.debug('stridx define: ' + s['define']) # Add a 'stridx_used' flag for strings which need a stridx. def metadata_add_string_used_stridx(strlist, used_stridx_meta): defs_needed = {} defs_found = {} for s in used_stridx_meta['used_stridx_defines']: defs_needed[s] = True # strings whose define is referenced for s in strlist: if s.has_key('define') and defs_needed.has_key(s['define']): s['stridx_used'] = True defs_found[s['define']] = True # duk_lexer.h needs all reserved words for s in strlist: if s.get('reserved_word', False): s['stridx_used'] = True # ensure all needed defines are provided defs_found['DUK_STRIDX_START_RESERVED'] = True # special defines provided automatically defs_found['DUK_STRIDX_START_STRICT_RESERVED'] = True defs_found['DUK_STRIDX_END_RESERVED'] = True defs_found['DUK_STRIDX_TO_TOK'] = True for k in sorted(defs_needed.keys()): if not defs_found.has_key(k): raise Exception('source code needs define %s not provided by strings' % repr(k)) # Merge duplicate strings in string metadata. def metadata_merge_string_entries(strlist): # The raw string list may contain duplicates so merge entries. # The list is processed in reverse because the last entry should # "win" and keep its place (this matters for reserved words). strs = [] str_map = {} # plain string -> object in strs[] tmp = copy.deepcopy(strlist) tmp.reverse() for s in tmp: prev = str_map.get(s['str']) if prev is not None: for k in s.keys(): if prev.has_key(k) and prev[k] != s[k]: raise Exception('fail to merge string entry, conflicting keys: %r <-> %r' % (prev, s)) prev[k] = s[k] else: strs.append(s) str_map[s['str']] = s strs.reverse() return strs # Order builtin strings (strings with a stridx) into an order satisfying # multiple constraints. def metadata_order_builtin_strings(input_strlist, keyword_list, strip_unused_stridx=False): # Strings are ordered in the result as follows: # 1. Non-reserved words requiring 8-bit indices # 2. Non-reserved words not requiring 8-bit indices # 3. Reserved words in non-strict mode only # 4. Reserved words in strict mode # # Reserved words must follow an exact order because they are # translated to/from token numbers by addition/subtraction. # Some strings require an 8-bit index and must be in the # beginning. tmp_strs = [] for s in copy.deepcopy(input_strlist): if not s.get('stridx_used', False): # Drop strings which are not actually needed by src-input/*.(c|h). # Such strings won't be in heap->strs[] or ROM legacy list. pass else: tmp_strs.append(s) # The reserved word list must match token order in duk_lexer.h # exactly, so pluck them out first. str_index = {} kw_index = {} keywords = [] strs = [] for idx,s in enumerate(tmp_strs): str_index[s['str']] = s for idx,s in enumerate(keyword_list): keywords.append(str_index[s]) kw_index[s] = True for idx,s in enumerate(tmp_strs): if not kw_index.has_key(s['str']): strs.append(s) # Sort the strings by category number; within category keep # previous order. for idx,s in enumerate(strs): s['_idx'] = idx # for ensuring stable sort def req8Bit(s): return s.get('class_name', False) # currently just class names def getCat(s): req8 = req8Bit(s) if s.get('reserved_word', False): # XXX: unused path now, because keywords are "plucked out" # explicitly. assert(not req8) if s.get('future_reserved_word_strict', False): return 4 else: return 3 elif req8: return 1 else: return 2 def sortCmp(a,b): return cmp( (getCat(a),a['_idx']), (getCat(b),b['_idx']) ) strs.sort(cmp=sortCmp) for idx,s in enumerate(strs): # Remove temporary _idx properties del s['_idx'] for idx,s in enumerate(strs): if req8Bit(s) and i >= 256: raise Exception('8-bit string index not satisfied: ' + repr(s)) return strs + keywords # Dump metadata into a JSON file. def dump_metadata(meta, fn): tmp = json.dumps(recursive_bytes_to_strings(meta), indent=4) with open(fn, 'wb') as f: f.write(tmp) logger.debug('Wrote metadata dump to %s' % fn) # Main metadata loading function: load metadata from multiple sources, # merge and normalize, prepare various indexes etc. def load_metadata(opts, rom=False, build_info=None, active_opts=None): # Load built-in strings and objects. with open(opts.strings_metadata, 'rb') as f: strings_metadata = recursive_strings_to_bytes(yaml.load(f)) with open(opts.objects_metadata, 'rb') as f: objects_metadata = recursive_strings_to_bytes(yaml.load(f)) # Merge strings and objects metadata as simple top level key merge. meta = {} for k in objects_metadata.keys(): meta[k] = objects_metadata[k] for k in strings_metadata.keys(): meta[k] = strings_metadata[k] # Add user objects. user_meta = {} for fn in opts.builtin_files: logger.debug('Merging user builtin metadata file %s' % fn) with open(fn, 'rb') as f: user_meta = recursive_strings_to_bytes(yaml.load(f)) metadata_merge_user_objects(meta, user_meta) # Remove disabled objects and properties. Also remove objects and # properties which are disabled in (known) active duk_config.h. metadata_remove_disabled(meta, active_opts) # Replace Symbol keys and property values with plain (encoded) strings. metadata_normalize_symbol_strings(meta) # Normalize 'nargs' and 'length' defaults. metadata_normalize_nargs_length(meta) # Normalize property attributes. metadata_normalize_property_attributes(meta) # Normalize property shorthand into full objects. metadata_normalize_shorthand(meta) # RAM top-level functions must have a 'name'. if not rom: metadata_normalize_ram_function_names(meta) # Add Duktape.version and (Duktape.env for ROM case). for o in meta['objects']: if o['id'] == 'bi_duktape': o['properties'].insert(0, { 'key': 'version', 'value': int(build_info['duk_version']), 'attributes': '' }) if rom: # Use a fixed (quite dummy for now) Duktape.env # when ROM builtins are in use. In the RAM case # this is added during global object initialization # based on config options in use. o['properties'].insert(0, { 'key': 'env', 'value': 'ROM', 'attributes': '' }) # Normalize property attributes (just in case shorthand handling # didn't add attributes to all properties). metadata_normalize_property_attributes(meta) # For ROM objects, mark all properties non-configurable. if rom: metadata_normalize_rom_property_attributes(meta) # Convert built-in function properties automatically into # lightfuncs if requested and function is eligible. if rom and opts.rom_auto_lightfunc: metadata_convert_lightfuncs(meta) # Create a list of objects needing a 'bidx'. Ensure 'objects' and # 'objects_bidx' match in order for shared length. metadata_prepare_objects_bidx(meta) # Merge duplicate strings. meta['strings'] = metadata_merge_string_entries(meta['strings']) # Prepare an ordered list of strings with 'stridx': # - Add a 'stridx_used' flag for strings which need an index in current code base # - Add a C define (DUK_STRIDX_xxx) for such strings # - Compute a stridx string order satisfying current runtime constraints # # The meta['strings_stridx'] result will be in proper order and stripped of # any strings which don't need a stridx. metadata_add_string_define_names(meta['strings'], meta['special_define_names']) with open(opts.used_stridx_metadata, 'rb') as f: metadata_add_string_used_stridx(meta['strings'], json.loads(f.read())) meta['strings_stridx'] = metadata_order_builtin_strings(meta['strings'], meta['reserved_word_token_order']) # For the ROM build: add any strings referenced by built-in objects # into the string list (not the 'stridx' list though): all strings # referenced by ROM objects must also be in ROM. if rom: for fn in opts.builtin_files: # XXX: awkward second pass with open(fn, 'rb') as f: user_meta = recursive_strings_to_bytes(yaml.load(f)) metadata_normalize_missing_strings(meta, user_meta) metadata_normalize_missing_strings(meta, {}) # in case no files # Check for orphan objects and remove them. metadata_remove_orphan_objects(meta) # Add final stridx and bidx indices to metadata objects and strings. idx = 0 for o in meta['objects']: if o.get('bidx_used', False): o['bidx'] = idx idx += 1 idx = 0 for s in meta['strings']: if s.get('stridx_used', False): s['stridx'] = idx idx += 1 # Prepare a filtered RAM top level object list, needed for technical # reasons during RAM init handling. if not rom: metadata_add_ram_filtered_object_list(meta) # Sanity check: object index must match 'bidx' for all objects # which have a runtime 'bidx'. This is assumed by e.g. RAM # thread init. for i,o in enumerate(meta['objects']): if i < len(meta['objects_bidx']): assert(meta['objects_bidx'][i] == meta['objects'][i]) if o.get('bidx', False): assert(o['bidx'] == i) # Create a set of helper lists and maps now that the metadata is # in its final form. meta['_strings_plain'] = [] meta['_strings_stridx_plain'] = [] meta['_stridx_to_string'] = {} meta['_idx_to_string'] = {} meta['_stridx_to_plain'] = {} meta['_idx_to_plain'] = {} meta['_string_to_stridx'] = {} meta['_plain_to_stridx'] = {} meta['_string_to_idx'] = {} meta['_plain_to_idx'] = {} meta['_define_to_stridx'] = {} meta['_stridx_to_define'] = {} meta['_is_plain_reserved_word'] = {} meta['_is_plain_strict_reserved_word'] = {} meta['_objid_to_object'] = {} meta['_objid_to_bidx'] = {} meta['_objid_to_idx'] = {} meta['_objid_to_ramidx'] = {} meta['_bidx_to_objid'] = {} meta['_idx_to_objid'] = {} meta['_bidx_to_object'] = {} meta['_idx_to_object'] = {} for i,s in enumerate(meta['strings']): assert(s['str'] not in meta['_strings_plain']) meta['_strings_plain'].append(s['str']) if s.get('reserved_word', False): meta['_is_plain_reserved_word'][s['str']] = True # includes also strict reserved words if s.get('future_reserved_word_strict', False): meta['_is_plain_strict_reserved_word'][s['str']] = True meta['_idx_to_string'][i] = s meta['_idx_to_plain'][i] = s['str'] meta['_plain_to_idx'][s['str']] = i #meta['_string_to_idx'][s] = i for i,s in enumerate(meta['strings_stridx']): assert(s.get('stridx_used', False) == True) meta['_strings_stridx_plain'].append(s['str']) meta['_stridx_to_string'][i] = s meta['_stridx_to_plain'][i] = s['str'] #meta['_string_to_stridx'][s] = i meta['_plain_to_stridx'][s['str']] = i meta['_define_to_stridx'][s['define']] = i meta['_stridx_to_define'][i] = s['define'] for i,o in enumerate(meta['objects']): meta['_objid_to_object'][o['id']] = o meta['_objid_to_idx'][o['id']] = i meta['_idx_to_objid'][i] = o['id'] meta['_idx_to_object'][i] = o for i,o in enumerate(meta['objects_bidx']): assert(o.get('bidx_used', False) == True) meta['_objid_to_bidx'][o['id']] = i assert(meta['_objid_to_bidx'][o['id']] == meta['_objid_to_idx'][o['id']]) meta['_bidx_to_objid'][i] = o['id'] meta['_bidx_to_object'][i] = o if meta.has_key('objects_ram_toplevel'): for i,o in enumerate(meta['objects_ram_toplevel']): meta['_objid_to_ramidx'][o['id']] = i # Dump stats. if rom: meta_name = 'ROM' else: meta_name = 'RAM' count_add_ref = 0 count_add_user = 0 for s in meta['strings']: if s.get('_auto_add_ref', False): count_add_ref += 1 if s.get('_auto_add_user', False): count_add_user += 1 count_add = count_add_ref + count_add_user logger.debug(('Prepared %s metadata: %d objects, %d objects with bidx, ' + \ '%d strings, %d strings with stridx, %d strings added ' + \ '(%d property key references, %d user strings)') % \ (meta_name, len(meta['objects']), len(meta['objects_bidx']), \ len(meta['strings']), len(meta['strings_stridx']), \ count_add, count_add_ref, count_add_user)) return meta # # Metadata helpers # # Magic values for Math built-in. math_onearg_magic = { 'fabs': 0, # BI_MATH_FABS_IDX 'acos': 1, # BI_MATH_ACOS_IDX 'asin': 2, # BI_MATH_ASIN_IDX 'atan': 3, # BI_MATH_ATAN_IDX 'ceil': 4, # BI_MATH_CEIL_IDX 'cos': 5, # BI_MATH_COS_IDX 'exp': 6, # BI_MATH_EXP_IDX 'floor': 7, # BI_MATH_FLOOR_IDX 'log': 8, # BI_MATH_LOG_IDX 'round': 9, # BI_MATH_ROUND_IDX 'sin': 10, # BI_MATH_SIN_IDX 'sqrt': 11, # BI_MATH_SQRT_IDX 'tan': 12, # BI_MATH_TAN_IDX 'cbrt': 13, # BI_MATH_CBRT_IDX 'log2': 14, # BI_MATH_LOG2_IDX 'log10': 15, # BI_MATH_LOG10_IDX 'trunc': 16, # BI_MATH_TRUNC_IDX } math_twoarg_magic = { 'atan2': 0, # BI_MATH_ATAN2_IDX 'pow': 1 # BI_MATH_POW_IDX } # Magic values for Array built-in. array_iter_magic = { 'every': 0, # BI_ARRAY_ITER_EVERY 'some': 1, # BI_ARRAY_ITER_SOME 'forEach': 2, # BI_ARRAY_ITER_FOREACH 'map': 3, # BI_ARRAY_ITER_MAP 'filter': 4 # BI_ARRAY_ITER_FILTER } # Magic value for typedarray/node.js buffer read field operations. def magic_readfield(elem, signed=None, bigendian=None, typedarray=None): # Must match duk__FLD_xxx in duk_bi_buffer.c elemnum = { '8bit': 0, '16bit': 1, '32bit': 2, 'float': 3, 'double': 4, 'varint': 5 }[elem] if signed == True: signednum = 1 elif signed == False: signednum = 0 else: raise Exception('missing "signed"') if bigendian == True: bigendiannum = 1 elif bigendian == False: bigendiannum = 0 else: raise Exception('missing "bigendian"') if typedarray == True: typedarraynum = 1 elif typedarray == False: typedarraynum = 0 else: raise Exception('missing "typedarray"') return elemnum + (signednum << 4) + (bigendiannum << 3) + (typedarraynum << 5) # Magic value for typedarray/node.js buffer write field operations. def magic_writefield(elem, signed=None, bigendian=None, typedarray=None): return magic_readfield(elem, signed=signed, bigendian=bigendian, typedarray=typedarray) # Magic value for typedarray constructors. def magic_typedarray_constructor(elem, shift): # Must match duk_hbufobj.h header elemnum = { 'uint8': 0, 'uint8clamped': 1, 'int8': 2, 'uint16': 3, 'int16': 4, 'uint32': 5, 'int32': 6, 'float32': 7, 'float64': 8 }[elem] return (elemnum << 2) + shift # Resolve a magic value from a YAML metadata element into an integer. def resolve_magic(elem, objid_to_bidx): if elem is None: return 0 if isinstance(elem, (int, long)): v = int(elem) if not (v >= -0x8000 and v <= 0x7fff): raise Exception('invalid plain value for magic: %s' % repr(v)) return v if not isinstance(elem, dict): raise Exception('invalid magic: %r' % elem) assert(elem.has_key('type')) if elem['type'] == 'bidx': # Maps to thr->builtins[]. v = elem['id'] return objid_to_bidx[v] elif elem['type'] == 'plain': v = elem['value'] if not (v >= -0x8000 and v <= 0x7fff): raise Exception('invalid plain value for magic: %s' % repr(v)) return v elif elem['type'] == 'math_onearg': return math_onearg_magic[elem['funcname']] elif elem['type'] == 'math_twoarg': return math_twoarg_magic[elem['funcname']] elif elem['type'] == 'array_iter': return array_iter_magic[elem['funcname']] elif elem['type'] == 'typedarray_constructor': return magic_typedarray_constructor(elem['elem'], elem['shift']) elif elem['type'] == 'buffer_readfield': return magic_readfield(elem['elem'], elem['signed'], elem['bigendian'], elem['typedarray']) elif elem['type'] == 'buffer_writefield': return magic_writefield(elem['elem'], elem['signed'], elem['bigendian'], elem['typedarray']) else: raise Exception('invalid magic type: %r' % elem) # Helper to find a property from a property list, remove it from the # property list, and return the removed property. def steal_prop(props, key, allow_accessor=True): for idx,prop in enumerate(props): if prop['key'] == key: if not (isinstance(prop['value'], dict) and prop['value']['type'] == 'accessor') or allow_accessor: return props.pop(idx) return None # # RAM initialization data # # Init data for built-in strings and objects. The init data for both # strings and objects is a bit-packed stream tailored to match the decoders # in duk_heap_alloc.c (strings) and duk_hthread_builtins.c (objects). # Various bitfield sizes are used to minimize the bitstream size without # resorting to actual, expensive compression. The goal is to minimize the # overall size of the init code and the init data. # # The built-in data created here is used to set up initial RAM versions # of the strings and objects. References to these objects are tracked in # heap->strs[] and thr->builtins[] which allows Duktape internals to refer # to built-ins e.g. as thr->builtins[DUK_BIDX_STRING_PROTOTYPE]. # # Not all strings and objects need to be reachable through heap->strs[] # or thr->builtins[]: the strings/objects that need to be in these arrays # is determined based on metadata and source code scanning. # # XXX: Reserved word stridxs could be made to match token numbers # directly so that a duk_stridx2token[] would not be needed. # Default property attributes. LENGTH_PROPERTY_ATTRIBUTES = 'c' ACCESSOR_PROPERTY_ATTRIBUTES = 'c' DEFAULT_DATA_PROPERTY_ATTRIBUTES = 'wc' DEFAULT_FUNC_PROPERTY_ATTRIBUTES = 'wc' # Encoding constants (must match duk_hthread_builtins.c). PROP_FLAGS_BITS = 3 LENGTH_PROP_BITS = 3 NARGS_BITS = 3 PROP_TYPE_BITS = 3 NARGS_VARARGS_MARKER = 0x07 PROP_TYPE_DOUBLE = 0 PROP_TYPE_STRING = 1 PROP_TYPE_STRIDX = 2 PROP_TYPE_BUILTIN = 3 PROP_TYPE_UNDEFINED = 4 PROP_TYPE_BOOLEAN_TRUE = 5 PROP_TYPE_BOOLEAN_FALSE = 6 PROP_TYPE_ACCESSOR = 7 # must match duk_hobject.h PROPDESC_FLAG_WRITABLE = (1 << 0) PROPDESC_FLAG_ENUMERABLE = (1 << 1) PROPDESC_FLAG_CONFIGURABLE = (1 << 2) PROPDESC_FLAG_ACCESSOR = (1 << 3) # unused now # Class names, numeric indices must match duk_hobject.h class numbers. class_names = [ 'Unused', 'Object', 'Array', 'Function', 'Arguments', 'Boolean', 'Date', 'Error', 'JSON', 'Math', 'Number', 'RegExp', 'String', 'global', 'Symbol', 'ObjEnv', 'DecEnv', 'Pointer', 'Thread' # Remaining class names are not currently needed. ] class2num = {} for i,v in enumerate(class_names): class2num[v] = i # Map class name to a class number. def class_to_number(x): return class2num[x] # Bitpack a string into a format shared by heap and thread init data. def bitpack_string(be, s, stats=None): # Strings are encoded as follows: a string begins in lowercase # mode and recognizes the following 5-bit symbols: # # 0-25 'a' ... 'z' or 'A' ... 'Z' depending on uppercase mode # 26-31 special controls, see code below LOOKUP1 = 26 LOOKUP2 = 27 SWITCH1 = 28 SWITCH = 29 UNUSED1 = 30 EIGHTBIT = 31 LOOKUP = '0123456789_ \x82\x80"{' # special characters assert(len(LOOKUP) == 16) # support up to 256 byte strings now, cases above ~30 bytes are very # rare, so favor short strings in encoding if len(s) <= 30: be.bits(len(s), 5) else: be.bits(31, 5) be.bits(len(s), 8) # 5-bit character, mode specific mode = 'lowercase' for idx, c in enumerate(s): # This encoder is not that optimal, but good enough for now. islower = (ord(c) >= ord('a') and ord(c) <= ord('z')) isupper = (ord(c) >= ord('A') and ord(c) <= ord('Z')) islast = (idx == len(s) - 1) isnextlower = False isnextupper = False if not islast: c2 = s[idx+1] isnextlower = (ord(c2) >= ord('a') and ord(c2) <= ord('z')) isnextupper = (ord(c2) >= ord('A') and ord(c2) <= ord('Z')) # XXX: Add back special handling for hidden or other symbols? if islower and mode == 'lowercase': be.bits(ord(c) - ord('a'), 5) if stats is not None: stats['n_optimal'] += 1 elif isupper and mode == 'uppercase': be.bits(ord(c) - ord('A'), 5) if stats is not None: stats['n_optimal'] += 1 elif islower and mode == 'uppercase': if isnextlower: be.bits(SWITCH, 5) be.bits(ord(c) - ord('a'), 5) mode = 'lowercase' if stats is not None: stats['n_switch'] += 1 else: be.bits(SWITCH1, 5) be.bits(ord(c) - ord('a'), 5) if stats is not None: stats['n_switch1'] += 1 elif isupper and mode == 'lowercase': if isnextupper: be.bits(SWITCH, 5) be.bits(ord(c) - ord('A'), 5) mode = 'uppercase' if stats is not None: stats['n_switch'] += 1 else: be.bits(SWITCH1, 5) be.bits(ord(c) - ord('A'), 5) if stats is not None: stats['n_switch1'] += 1 elif c in LOOKUP: idx = LOOKUP.find(c) if idx >= 8: be.bits(LOOKUP2, 5) be.bits(idx - 8, 3) if stats is not None: stats['n_lookup2'] += 1 else: be.bits(LOOKUP1, 5) be.bits(idx, 3) if stats is not None: stats['n_lookup1'] += 1 elif ord(c) >= 0 and ord(c) <= 255: logger.debug('eightbit encoding for %d (%s)' % (ord(c), c)) be.bits(EIGHTBIT, 5) be.bits(ord(c), 8) if stats is not None: stats['n_eightbit'] += 1 else: raise Exception('internal error in bitpacking a string') # Generate bit-packed RAM string init data. def gen_ramstr_initdata_bitpacked(meta): be = dukutil.BitEncoder() maxlen = 0 stats = { 'n_optimal': 0, 'n_lookup1': 0, 'n_lookup2': 0, 'n_switch1': 0, 'n_switch': 0, 'n_eightbit': 0 } for s_obj in meta['strings_stridx']: s = s_obj['str'] if len(s) > maxlen: maxlen = len(s) bitpack_string(be, s, stats) # end marker not necessary, C code knows length from define if be._varuint_count > 0: logger.debug('Varuint distribution:') logger.debug(json.dumps(be._varuint_dist[0:1024])) logger.debug('Varuint encoding categories: %r' % be._varuint_cats) logger.debug('Varuint efficiency: %f bits/value' % (float(be._varuint_bits) / float(be._varuint_count))) res = be.getByteString() logger.debug(('%d ram strings, %d bytes of string init data, %d maximum string length, ' + \ 'encoding: optimal=%d,lookup1=%d,lookup2=%d,switch1=%d,switch=%d,eightbit=%d') % \ (len(meta['strings_stridx']), len(res), maxlen, \ stats['n_optimal'], stats['n_lookup1'], stats['n_lookup2'], stats['n_switch1'], stats['n_switch'], stats['n_eightbit'])) return res, maxlen # Functions to emit string-related source/header parts. def emit_ramstr_source_strinit_data(genc, strdata): genc.emitArray(strdata, 'duk_strings_data', visibility='DUK_INTERNAL', typename='duk_uint8_t', intvalues=True, const=True, size=len(strdata)) def emit_ramstr_header_strinit_defines(genc, meta, strdata, strmaxlen): genc.emitLine('#if !defined(DUK_SINGLE_FILE)') genc.emitLine('DUK_INTERNAL_DECL const duk_uint8_t duk_strings_data[%d];' % len(strdata)) genc.emitLine('#endif /* !DUK_SINGLE_FILE */') genc.emitDefine('DUK_STRDATA_MAX_STRLEN', strmaxlen) genc.emitDefine('DUK_STRDATA_DATA_LENGTH', len(strdata)) # This is used for both RAM and ROM strings. def emit_header_stridx_defines(genc, meta): strlist = meta['strings_stridx'] for idx,s in enumerate(strlist): genc.emitDefine(s['define'], idx, repr(s['str'])) defname = s['define'].replace('_STRIDX','_HEAP_STRING') genc.emitDefine(defname + '(heap)', 'DUK_HEAP_GET_STRING((heap),%s)' % s['define']) defname = s['define'].replace('_STRIDX', '_HTHREAD_STRING') genc.emitDefine(defname + '(thr)', 'DUK_HTHREAD_GET_STRING((thr),%s)' % s['define']) idx_start_reserved = None idx_start_strict_reserved = None for idx,s in enumerate(strlist): if idx_start_reserved is None and s.get('reserved_word', False): idx_start_reserved = idx if idx_start_strict_reserved is None and s.get('future_reserved_word_strict', False): idx_start_strict_reserved = idx assert(idx_start_reserved is not None) assert(idx_start_strict_reserved is not None) genc.emitLine('') genc.emitDefine('DUK_HEAP_NUM_STRINGS', len(strlist)) genc.emitDefine('DUK_STRIDX_START_RESERVED', idx_start_reserved) genc.emitDefine('DUK_STRIDX_START_STRICT_RESERVED', idx_start_strict_reserved) genc.emitDefine('DUK_STRIDX_END_RESERVED', len(strlist), comment='exclusive endpoint') genc.emitLine('') genc.emitLine('/* To convert a heap stridx to a token number, subtract') genc.emitLine(' * DUK_STRIDX_START_RESERVED and add DUK_TOK_START_RESERVED.') genc.emitLine(' */') # Encode property flags for RAM initializers. def encode_property_flags(flags): # Note: must match duk_hobject.h res = 0 nflags = 0 if 'w' in flags: nflags += 1 res = res | PROPDESC_FLAG_WRITABLE if 'e' in flags: nflags += 1 res = res | PROPDESC_FLAG_ENUMERABLE if 'c' in flags: nflags += 1 res = res | PROPDESC_FLAG_CONFIGURABLE if 'a' in flags: nflags += 1 res = res | PROPDESC_FLAG_ACCESSOR if nflags != len(flags): raise Exception('unsupported flags: %s' % repr(flags)) return res # Generate RAM object initdata for an object (but not its properties). def gen_ramobj_initdata_for_object(meta, be, bi, string_to_stridx, natfunc_name_to_natidx, objid_to_bidx): def _stridx(strval): stridx = string_to_stridx[strval] be.varuint(stridx) def _stridx_or_string(strval): stridx = string_to_stridx.get(strval) if stridx is not None: be.varuint(stridx + 1) else: be.varuint(0) bitpack_string(be, strval) def _natidx(native_name): natidx = natfunc_name_to_natidx[native_name] be.varuint(natidx) class_num = class_to_number(bi['class']) be.varuint(class_num) props = [x for x in bi['properties']] # clone prop_proto = steal_prop(props, 'prototype') prop_constr = steal_prop(props, 'constructor') prop_name = steal_prop(props, 'name', allow_accessor=False) prop_length = steal_prop(props, 'length', allow_accessor=False) length = -1 # default value -1 signifies varargs if prop_length is not None: assert(isinstance(prop_length['value'], int)) length = prop_length['value'] be.bits(1, 1) # flag: have length be.bits(length, LENGTH_PROP_BITS) else: be.bits(0, 1) # flag: no length # The attributes for 'length' are standard ("none") except for # Array.prototype.length which must be writable (this is handled # separately in duk_hthread_builtins.c). len_attrs = LENGTH_PROPERTY_ATTRIBUTES if prop_length is not None: len_attrs = prop_length['attributes'] if len_attrs != LENGTH_PROPERTY_ATTRIBUTES: # Attributes are assumed to be the same, except for Array.prototype. if bi['class'] != 'Array': # Array.prototype is the only one with this class raise Exception('non-default length attribute for unexpected object') # For 'Function' classed objects, emit the native function stuff. # Unfortunately this is more or less a copy of what we do for # function properties now. This should be addressed if a rework # on the init format is done. if bi['class'] == 'Function': _natidx(bi['native']) if bi.get('varargs', False): be.bits(1, 1) # flag: non-default nargs be.bits(NARGS_VARARGS_MARKER, NARGS_BITS) elif bi.has_key('nargs') and bi['nargs'] != length: be.bits(1, 1) # flag: non-default nargs be.bits(bi['nargs'], NARGS_BITS) else: assert(length is not None) be.bits(0, 1) # flag: default nargs OK # All Function-classed global level objects are callable # (have [[Call]]) but not all are constructable (have # [[Construct]]). Flag that. assert(bi.has_key('callable')) assert(bi['callable'] == True) assert(prop_name is not None) assert(isinstance(prop_name['value'], str)) _stridx_or_string(prop_name['value']) if bi.get('constructable', False): be.bits(1, 1) # flag: constructable else: be.bits(0, 1) # flag: not constructable # DUK_HOBJECT_FLAG_SPECIAL_CALL is handled at runtime without init data. # Convert signed magic to 16-bit unsigned for encoding magic = resolve_magic(bi.get('magic'), objid_to_bidx) & 0xffff assert(magic >= 0) assert(magic <= 0xffff) be.varuint(magic) # Generate RAM object initdata for an object's properties. def gen_ramobj_initdata_for_props(meta, be, bi, string_to_stridx, natfunc_name_to_natidx, objid_to_bidx, double_byte_order): count_normal_props = 0 count_function_props = 0 def _bidx(bi_id): be.varuint(objid_to_bidx[bi_id]) def _bidx_or_none(bi_id): if bi_id is None: be.varuint(0) else: be.varuint(objid_to_bidx[bi_id] + 1) def _stridx(strval): stridx = string_to_stridx[strval] be.varuint(stridx) def _stridx_or_string(strval): stridx = string_to_stridx.get(strval) if stridx is not None: be.varuint(stridx + 1) else: be.varuint(0) bitpack_string(be, strval) def _natidx(native_name): if native_name is None: natidx = 0 # 0 is NULL in the native functions table, denotes missing function else: natidx = natfunc_name_to_natidx[native_name] be.varuint(natidx) props = [x for x in bi['properties']] # clone # internal prototype: not an actual property so not in property list if bi.has_key('internal_prototype'): _bidx_or_none(bi['internal_prototype']) else: _bidx_or_none(None) # external prototype: encoded specially, steal from property list prop_proto = steal_prop(props, 'prototype') if prop_proto is not None: assert(prop_proto['value']['type'] == 'object') assert(prop_proto['attributes'] == '') _bidx_or_none(prop_proto['value']['id']) else: _bidx_or_none(None) # external constructor: encoded specially, steal from property list prop_constr = steal_prop(props, 'constructor') if prop_constr is not None: assert(prop_constr['value']['type'] == 'object') assert(prop_constr['attributes'] == 'wc') _bidx_or_none(prop_constr['value']['id']) else: _bidx_or_none(None) # name: encoded specially for function objects, so steal and ignore here if bi['class'] == 'Function': prop_name = steal_prop(props, 'name', allow_accessor=False) assert(prop_name is not None) assert(isinstance(prop_name['value'], str)) assert(prop_name['attributes'] == 'c') # length: encoded specially, so steal and ignore prop_proto = steal_prop(props, 'length', allow_accessor=False) # Date.prototype.toGMTString needs special handling and is handled # directly in duk_hthread_builtins.c; so steal and ignore here. if bi['id'] == 'bi_date_prototype': prop_togmtstring = steal_prop(props, 'toGMTString') assert(prop_togmtstring is not None) logger.debug('Stole Date.prototype.toGMTString') # Split properties into non-builtin functions and other properties. # This split is a bit arbitrary, but is used to reduce flag bits in # the bit stream. values = [] functions = [] for prop in props: if isinstance(prop['value'], dict) and \ prop['value']['type'] == 'object' and \ metadata_lookup_object(meta, prop['value']['id']).has_key('native') and \ not metadata_lookup_object(meta, prop['value']['id']).has_key('bidx'): functions.append(prop) else: values.append(prop) be.varuint(len(values)) for valspec in values: count_normal_props += 1 val = valspec['value'] _stridx_or_string(valspec['key']) # Attribute check doesn't check for accessor flag; that is now # automatically set by C code when value is an accessor type. # Accessors must not have 'writable', so they'll always have # non-default attributes (less footprint than adding a different # default). default_attrs = DEFAULT_DATA_PROPERTY_ATTRIBUTES attrs = valspec.get('attributes', default_attrs) attrs = attrs.replace('a', '') # ram bitstream doesn't encode 'accessor' attribute if attrs != default_attrs: logger.debug('non-default attributes: %s -> %r (default %r)' % (valspec['key'], attrs, default_attrs)) be.bits(1, 1) # flag: have custom attributes be.bits(encode_property_flags(attrs), PROP_FLAGS_BITS) else: be.bits(0, 1) # flag: no custom attributes if val is None: logger.warning('RAM init data format doesn\'t support "null" now, value replaced with "undefined": %r' % valspec) #raise Exception('RAM init format doesn\'t support a "null" value now') be.bits(PROP_TYPE_UNDEFINED, PROP_TYPE_BITS) elif isinstance(val, bool): if val == True: be.bits(PROP_TYPE_BOOLEAN_TRUE, PROP_TYPE_BITS) else: be.bits(PROP_TYPE_BOOLEAN_FALSE, PROP_TYPE_BITS) elif isinstance(val, (float, int)) or isinstance(val, dict) and val['type'] == 'double': # Avoid converting a manually specified NaN temporarily into # a float to avoid risk of e.g. NaN being replaced by another. if isinstance(val, dict): val = val['bytes'].decode('hex') assert(len(val) == 8) else: val = struct.pack('>d', float(val)) be.bits(PROP_TYPE_DOUBLE, PROP_TYPE_BITS) # encoding of double must match target architecture byte order indexlist = { 'big': [ 0, 1, 2, 3, 4, 5, 6, 7 ], 'little': [ 7, 6, 5, 4, 3, 2, 1, 0 ], 'mixed': [ 3, 2, 1, 0, 7, 6, 5, 4 ] # some arm platforms }[double_byte_order] data = ''.join([ val[indexlist[idx]] for idx in xrange(8) ]) logger.debug('DOUBLE: %s -> %s' % (val.encode('hex'), data.encode('hex'))) if len(data) != 8: raise Exception('internal error') be.string(data) elif isinstance(val, str) or isinstance(val, unicode): if isinstance(val, unicode): # Note: non-ASCII characters will not currently work, # because bits/char is too low. val = val.encode('utf-8') if string_to_stridx.has_key(val): # String value is in built-in string table -> encode # using a string index. This saves some space, # especially for the 'name' property of errors # ('EvalError' etc). be.bits(PROP_TYPE_STRIDX, PROP_TYPE_BITS) _stridx(val) else: # Not in string table, bitpack string value as is. be.bits(PROP_TYPE_STRING, PROP_TYPE_BITS) bitpack_string(be, val) elif isinstance(val, dict): if val['type'] == 'object': be.bits(PROP_TYPE_BUILTIN, PROP_TYPE_BITS) _bidx(val['id']) elif val['type'] == 'undefined': be.bits(PROP_TYPE_UNDEFINED, PROP_TYPE_BITS) elif val['type'] == 'accessor': be.bits(PROP_TYPE_ACCESSOR, PROP_TYPE_BITS) getter_natfun = None setter_natfun = None getter_magic = 0 setter_magic = 0 if val.has_key('getter_id'): getter_fn = metadata_lookup_object(meta, val['getter_id']) getter_natfun = getter_fn['native'] assert(getter_fn['nargs'] == 0) getter_magic = getter_fn['magic'] if val.has_key('setter_id'): setter_fn = metadata_lookup_object(meta, val['setter_id']) setter_natfun = setter_fn['native'] assert(setter_fn['nargs'] == 1) setter_magic = setter_fn['magic'] if getter_natfun is not None and setter_natfun is not None: assert(getter_magic == setter_magic) _natidx(getter_natfun) _natidx(setter_natfun) be.varuint(getter_magic) elif val['type'] == 'lightfunc': logger.warning('RAM init data format doesn\'t support "lightfunc" now, value replaced with "undefined": %r' % valspec) be.bits(PROP_TYPE_UNDEFINED, PROP_TYPE_BITS) else: raise Exception('unsupported value: %s' % repr(val)) else: raise Exception('unsupported value: %s' % repr(val)) be.varuint(len(functions)) for funprop in functions: count_function_props += 1 funobj = metadata_lookup_object(meta, funprop['value']['id']) prop_len = metadata_lookup_property(funobj, 'length') assert(prop_len is not None) assert(isinstance(prop_len['value'], (int))) length = prop_len['value'] # XXX: this doesn't currently handle a function .name != its key # At least warn about it here. Or maybe generate the correct name # at run time (it's systematic at the moment, @@toPrimitive has the # name "[Symbol.toPrimitive]" which can be computed from the symbol # internal representation. _stridx_or_string(funprop['key']) _natidx(funobj['native']) be.bits(length, LENGTH_PROP_BITS) if funobj.get('varargs', False): be.bits(1, 1) # flag: non-default nargs be.bits(NARGS_VARARGS_MARKER, NARGS_BITS) elif funobj.has_key('nargs') and funobj['nargs'] != length: be.bits(1, 1) # flag: non-default nargs be.bits(funobj['nargs'], NARGS_BITS) else: be.bits(0, 1) # flag: default nargs OK # XXX: make this check conditional to minimize bit count # (there are quite a lot of function properties) # Convert signed magic to 16-bit unsigned for encoding magic = resolve_magic(funobj.get('magic'), objid_to_bidx) & 0xffff assert(magic >= 0) assert(magic <= 0xffff) be.varuint(magic) default_attrs = DEFAULT_FUNC_PROPERTY_ATTRIBUTES attrs = funprop.get('attributes', default_attrs) attrs = attrs.replace('a', '') # ram bitstream doesn't encode 'accessor' attribute if attrs != default_attrs: logger.debug('non-default attributes: %s -> %r (default %r)' % (funprop['key'], attrs, default_attrs)) be.bits(1, 1) # flag: have custom attributes be.bits(encode_property_flags(attrs), PROP_FLAGS_BITS) else: be.bits(0, 1) # flag: no custom attributes return count_normal_props, count_function_props # Get helper maps for RAM objects. def get_ramobj_native_func_maps(meta): # Native function list and index native_funcs_found = {} native_funcs = [] natfunc_name_to_natidx = {} native_funcs.append(None) # natidx 0 is reserved for NULL for o in meta['objects']: if o.has_key('native'): native_funcs_found[o['native']] = True for v in o['properties']: val = v['value'] if isinstance(val, dict): if val['type'] == 'accessor': if val.has_key('getter_id'): getter = metadata_lookup_object(meta, val['getter_id']) native_funcs_found[getter['native']] = True if val.has_key('setter_id'): setter = metadata_lookup_object(meta, val['setter_id']) native_funcs_found[setter['native']] = True if val['type'] == 'object': target = metadata_lookup_object(meta, val['id']) if target.has_key('native'): native_funcs_found[target['native']] = True if val['type'] == 'lightfunc': # No lightfunc support for RAM initializer now. pass for idx,k in enumerate(sorted(native_funcs_found.keys())): natfunc_name_to_natidx[k] = len(native_funcs) native_funcs.append(k) # native func names return native_funcs, natfunc_name_to_natidx # Generate bit-packed RAM object init data. def gen_ramobj_initdata_bitpacked(meta, native_funcs, natfunc_name_to_natidx, double_byte_order): # RAM initialization is based on a specially filtered list of top # level objects which includes objects with 'bidx' and objects # which aren't handled as inline values in the init bitstream. objlist = meta['objects_ram_toplevel'] objid_to_idx = meta['_objid_to_ramidx'] objid_to_object = meta['_objid_to_object'] # This index is valid even for filtered object list string_index = meta['_plain_to_stridx'] # Generate bitstream be = dukutil.BitEncoder() count_builtins = 0 count_normal_props = 0 count_function_props = 0 for o in objlist: count_builtins += 1 gen_ramobj_initdata_for_object(meta, be, o, string_index, natfunc_name_to_natidx, objid_to_idx) for o in objlist: count_obj_normal, count_obj_func = gen_ramobj_initdata_for_props(meta, be, o, string_index, natfunc_name_to_natidx, objid_to_idx, double_byte_order) count_normal_props += count_obj_normal count_function_props += count_obj_func if be._varuint_count > 0: logger.debug('varuint distribution:') logger.debug(json.dumps(be._varuint_dist[0:1024])) logger.debug('Varuint encoding categories: %r' % be._varuint_cats) logger.debug('Varuint efficiency: %f bits/value' % (float(be._varuint_bits) / float(be._varuint_count))) romobj_init_data = be.getByteString() #logger.debug(repr(romobj_init_data)) #logger.debug(len(romobj_init_data)) logger.debug('%d ram builtins, %d normal properties, %d function properties, %d bytes of object init data' % \ (count_builtins, count_normal_props, count_function_props, len(romobj_init_data))) return romobj_init_data # Functions to emit object-related source/header parts. def emit_ramobj_source_nativefunc_array(genc, native_func_list): genc.emitLine('/* native functions: %d */' % len(native_func_list)) genc.emitLine('DUK_INTERNAL const duk_c_function duk_bi_native_functions[%d] = {' % len(native_func_list)) for i in native_func_list: # The function pointer cast here makes BCC complain about # "initializer too complicated", so omit the cast. #genc.emitLine('\t(duk_c_function) %s,' % i) if i is None: genc.emitLine('\tNULL,') else: genc.emitLine('\t%s,' % i) genc.emitLine('};') def emit_ramobj_source_objinit_data(genc, init_data): genc.emitArray(init_data, 'duk_builtins_data', visibility='DUK_INTERNAL', typename='duk_uint8_t', intvalues=True, const=True, size=len(init_data)) def emit_ramobj_header_nativefunc_array(genc, native_func_list): genc.emitLine('#if !defined(DUK_SINGLE_FILE)') genc.emitLine('DUK_INTERNAL_DECL const duk_c_function duk_bi_native_functions[%d];' % len(native_func_list)) genc.emitLine('#endif /* !DUK_SINGLE_FILE */') def emit_ramobj_header_objects(genc, meta): objlist = meta['objects_bidx'] for idx,o in enumerate(objlist): defname = 'DUK_BIDX_' + '_'.join(o['id'].upper().split('_')[1:]) # bi_foo_bar -> FOO_BAR genc.emitDefine(defname, idx) genc.emitDefine('DUK_NUM_BUILTINS', len(objlist)) genc.emitDefine('DUK_NUM_BIDX_BUILTINS', len(objlist)) # Objects with 'bidx' genc.emitDefine('DUK_NUM_ALL_BUILTINS', len(meta['objects_ram_toplevel'])) # Objects with 'bidx' + temps needed in init def emit_ramobj_header_initdata(genc, init_data): genc.emitLine('#if !defined(DUK_SINGLE_FILE)') genc.emitLine('DUK_INTERNAL_DECL const duk_uint8_t duk_builtins_data[%d];' % len(init_data)) genc.emitLine('#endif /* !DUK_SINGLE_FILE */') genc.emitDefine('DUK_BUILTINS_DATA_LENGTH', len(init_data)) # # ROM init data # # Compile-time initializers for ROM strings and ROM objects. This involves # a lot of small details: # # - Several variants are needed for different options: unpacked vs. # packed duk_tval, endianness, string hash in use, etc). # # - Static initializers must represent objects of different size. For # example, separate structs are needed for property tables of different # size or value typing. # # - Union initializers cannot be used portable because they're only # available in C99 and above. # # - Initializers must use 'const' correctly to ensure that the entire # initialization data will go into ROM (read-only data section). # Const pointers etc will need to be cast into non-const pointers at # some point to properly mix with non-const RAM pointers, so a portable # const losing cast is needed. # # - C++ doesn't allow forward declaration of "static const" structures # which is problematic because there are cyclical const structures. # # Get string hash initializers; need to compute possible string hash variants # which will match runtime values. def rom_get_strhash16_macro(val): hash16le = dukutil.duk_heap_hashstring_dense(val, DUK__FIXED_HASH_SEED, big_endian=False, strhash16=True) hash16be = dukutil.duk_heap_hashstring_dense(val, DUK__FIXED_HASH_SEED, big_endian=True, strhash16=True) hash16sparse = dukutil.duk_heap_hashstring_sparse(val, DUK__FIXED_HASH_SEED, strhash16=True) return 'DUK__STRHASH16(%dU,%dU,%dU)' % (hash16le, hash16be, hash16sparse) def rom_get_strhash32_macro(val): hash32le = dukutil.duk_heap_hashstring_dense(val, DUK__FIXED_HASH_SEED, big_endian=False, strhash16=False) hash32be = dukutil.duk_heap_hashstring_dense(val, DUK__FIXED_HASH_SEED, big_endian=True, strhash16=False) hash32sparse = dukutil.duk_heap_hashstring_sparse(val, DUK__FIXED_HASH_SEED, strhash16=False) return 'DUK__STRHASH32(%dUL,%dUL,%dUL)' % (hash32le, hash32be, hash32sparse) # Get string character .length; must match runtime .length computation. def rom_charlen(x): return dukutil.duk_unicode_unvalidated_utf8_length(x) # Get an initializer type and initializer literal for a specified value # (expressed in YAML metadata format). The types and initializers depend # on declarations emitted before the initializers, and in several cases # use a macro to hide the selection between several initializer variants. def rom_get_value_initializer(meta, val, bi_str_map, bi_obj_map): def double_bytes_initializer(val): # Portable and exact float initializer. assert(isinstance(val, str) and len(val) == 16) # hex encoded bytes val = val.decode('hex') tmp = [] for i in xrange(8): t = ord(val[i]) if t >= 128: tmp.append('%dU' % t) else: tmp.append('%d' % t) return 'DUK__DBLBYTES(' + ','.join(tmp) + ')' def tval_number_initializer(val): return 'DUK__TVAL_NUMBER(%s)' % double_bytes_initializer(val) v = val['value'] if v is None: init_type = 'duk_rom_tval_null' init_lit = 'DUK__TVAL_NULL()' elif isinstance(v, (bool)): init_type = 'duk_rom_tval_boolean' bval = 0 if v: bval = 1 init_lit = 'DUK__TVAL_BOOLEAN(%d)' % bval elif isinstance(v, (int, float)): fval = struct.pack('>d', float(v)).encode('hex') init_type = 'duk_rom_tval_number' init_lit = tval_number_initializer(fval) elif isinstance(v, (str, unicode)): init_type = 'duk_rom_tval_string' init_lit = 'DUK__TVAL_STRING(&%s)' % bi_str_map[v] elif isinstance(v, (dict)): if v['type'] == 'double': init_type = 'duk_rom_tval_number' init_lit = tval_number_initializer(v['bytes']) elif v['type'] == 'undefined': init_type = 'duk_rom_tval_undefined' init_lit = 'DUK__TVAL_UNDEFINED()' elif v['type'] == 'null': init_type = 'duk_rom_tval_null' init_lit = 'DUK__TVAL_UNDEFINED()' elif v['type'] == 'object': init_type = 'duk_rom_tval_object' init_lit = 'DUK__TVAL_OBJECT(&%s)' % bi_obj_map[v['id']] elif v['type'] == 'accessor': getter_ref = 'NULL' setter_ref = 'NULL' if v.has_key('getter_id'): getter_object = metadata_lookup_object(meta, v['getter_id']) getter_ref = '&%s' % bi_obj_map[getter_object['id']] if v.has_key('setter_id'): setter_object = metadata_lookup_object(meta, v['setter_id']) setter_ref = '&%s' % bi_obj_map[setter_object['id']] init_type = 'duk_rom_tval_accessor' init_lit = 'DUK__TVAL_ACCESSOR(%s, %s)' % (getter_ref, setter_ref) elif v['type'] == 'lightfunc': # Match DUK_LFUNC_FLAGS_PACK() in duk_tval.h. if v.has_key('length'): assert(v['length'] >= 0 and v['length'] <= 15) lf_length = v['length'] else: lf_length = 0 if v.get('varargs', True): lf_nargs = 15 # varargs marker else: assert(v['nargs'] >= 0 and v['nargs'] <= 14) lf_nargs = v['nargs'] if v.has_key('magic'): assert(v['magic'] >= -0x80 and v['magic'] <= 0x7f) lf_magic = v['magic'] & 0xff else: lf_magic = 0 lf_flags = (lf_magic << 8) + (lf_length << 4) + lf_nargs init_type = 'duk_rom_tval_lightfunc' init_lit = 'DUK__TVAL_LIGHTFUNC(%s, %dL)' % (v['native'], lf_flags) else: raise Exception('unhandled value: %r' % val) else: raise Exception('internal error: %r' % val) return init_type, init_lit # Helpers to get either initializer type or value only (not both). def rom_get_value_initializer_type(meta, val, bi_str_map, bi_obj_map): init_type, init_lit = rom_get_value_initializer(meta, val, bi_str_map, bi_obj_map) return init_type def rom_get_value_initializer_literal(meta, val, bi_str_map, bi_obj_map): init_type, init_lit = rom_get_value_initializer(meta, val, bi_str_map, bi_obj_map) return init_lit # Emit ROM strings source: structs/typedefs and their initializers. # Separate initialization structs are needed for strings of different # length. def rom_emit_strings_source(genc, meta): # Write built-in strings as code section initializers. strs = meta['_strings_plain'] # all strings, plain versions reserved_words = meta['_is_plain_reserved_word'] strict_reserved_words = meta['_is_plain_strict_reserved_word'] strs_needing_stridx = meta['strings_stridx'] # Sort used lengths and declare per-length initializers. lens = [] for v in strs: strlen = len(v) if strlen not in lens: lens.append(strlen) lens.sort() for strlen in lens: genc.emitLine('typedef struct duk_romstr_%d duk_romstr_%d; ' % (strlen, strlen) + 'struct duk_romstr_%d { duk_hstring hdr; duk_uint8_t data[%d]; };' % (strlen, strlen + 1)) genc.emitLine('') # String hash values depend on endianness and other factors, # use an initializer macro to select the appropriate hash. genc.emitLine('/* When unaligned access possible, 32-bit values are fetched using host order.') genc.emitLine(' * When unaligned access not possible, always simulate little endian order.') genc.emitLine(' * See: src-input/duk_util_hashbytes.c:duk_util_hashbytes().') genc.emitLine(' */') genc.emitLine('#if defined(DUK_USE_STRHASH_DENSE)') genc.emitLine('#if defined(DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS)') # XXX: config option to be reworked genc.emitLine('#if defined(DUK_USE_INTEGER_BE)') genc.emitLine('#define DUK__STRHASH16(hash16le,hash16be,hash16sparse) (hash16be)') genc.emitLine('#define DUK__STRHASH32(hash32le,hash32be,hash32sparse) (hash32be)') genc.emitLine('#else') genc.emitLine('#define DUK__STRHASH16(hash16le,hash16be,hash16sparse) (hash16le)') genc.emitLine('#define DUK__STRHASH32(hash32le,hash32be,hash32sparse) (hash32le)') genc.emitLine('#endif') genc.emitLine('#else') genc.emitLine('#define DUK__STRHASH16(hash16le,hash16be,hash16sparse) (hash16le)') genc.emitLine('#define DUK__STRHASH32(hash32le,hash32be,hash32sparse) (hash32le)') genc.emitLine('#endif') genc.emitLine('#else /* DUK_USE_STRHASH_DENSE */') genc.emitLine('#define DUK__STRHASH16(hash16le,hash16be,hash16sparse) (hash16sparse)') genc.emitLine('#define DUK__STRHASH32(hash32le,hash32be,hash32sparse) (hash32sparse)') genc.emitLine('#endif /* DUK_USE_STRHASH_DENSE */') # String header initializer macro, takes into account lowmem etc. genc.emitLine('#if defined(DUK_USE_HEAPPTR16)') genc.emitLine('#if !defined(DUK_USE_REFCOUNT16)') genc.emitLine('#error currently assumes DUK_USE_HEAPPTR16 and DUK_USE_REFCOUNT16 are both defined') genc.emitLine('#endif') genc.emitLine('#if defined(DUK_USE_HSTRING_CLEN)') genc.emitLine('#define DUK__STRINIT(heaphdr_flags,refcount,hash32,hash16,blen,clen,next) \\') genc.emitLine('\t{ { (heaphdr_flags) | ((hash16) << 16), DUK__REFCINIT((refcount)), (blen), (duk_hstring *) DUK_LOSE_CONST((next)) }, (clen) }') genc.emitLine('#else /* DUK_USE_HSTRING_CLEN */') genc.emitLine('#define DUK__STRINIT(heaphdr_flags,refcount,hash32,hash16,blen,clen,next) \\') genc.emitLine('\t{ { (heaphdr_flags) | ((hash16) << 16), DUK__REFCINIT((refcount)), (blen), (duk_hstring *) DUK_LOSE_CONST((next)) } }') genc.emitLine('#endif /* DUK_USE_HSTRING_CLEN */') genc.emitLine('#else /* DUK_USE_HEAPPTR16 */') genc.emitLine('#define DUK__STRINIT(heaphdr_flags,refcount,hash32,hash16,blen,clen,next) \\') genc.emitLine('\t{ { (heaphdr_flags), DUK__REFCINIT((refcount)), (duk_hstring *) DUK_LOSE_CONST((next)) }, (hash32), (blen), (clen) }') genc.emitLine('#endif /* DUK_USE_HEAPPTR16 */') # Organize ROM strings into a chained ROM string table. The ROM string # h_next link pointer is used for chaining just like for RAM strings but # in a separate string table. # # To avoid dealing with the different possible string hash algorithms, # use a much more trivial lookup key for ROM strings for now. romstr_hash = [] while len(romstr_hash) < ROMSTR_LOOKUP_SIZE: romstr_hash.append([]) for str_index,v in enumerate(strs): if len(v) > 0: rom_lookup_hash = ord(v[0]) + (len(v) << 4) else: rom_lookup_hash = 0 + (len(v) << 4) rom_lookup_hash = rom_lookup_hash & 0xff romstr_hash[rom_lookup_hash].append(v) romstr_next = {} # string -> the string's 'next' link for lst in romstr_hash: prev = None #print(repr(lst)) for v in lst: if prev is not None: romstr_next[prev] = v prev = v chain_lens = {} for lst in romstr_hash: chainlen = len(lst) if not chain_lens.has_key(chainlen): chain_lens[chainlen] = 0 chain_lens[chainlen] += 1 tmp = [] for k in sorted(chain_lens.keys()): tmp.append('%d: %d' % (k, chain_lens[k])) logger.info('ROM string table chain lengths: %s' % ', '.join(tmp)) bi_str_map = {} # string -> initializer variable name for str_index,v in enumerate(strs): bi_str_map[v] = 'duk_str_%d' % str_index # Emit string initializers. Emit the strings in an order which avoids # forward declarations for the h_next link pointers; const forward # declarations are a problem in C++. genc.emitLine('') for lst in romstr_hash: for v in reversed(lst): tmp = 'DUK_INTERNAL const duk_romstr_%d %s = {' % (len(v), bi_str_map[v]) flags = [ 'DUK_HTYPE_STRING', 'DUK_HEAPHDR_FLAG_READONLY', 'DUK_HEAPHDR_FLAG_REACHABLE', 'DUK_HSTRING_FLAG_PINNED_LITERAL' ] is_arridx = string_is_arridx(v) blen = len(v) clen = rom_charlen(v) if blen == clen: flags.append('DUK_HSTRING_FLAG_ASCII') if is_arridx: flags.append('DUK_HSTRING_FLAG_ARRIDX') if len(v) >= 1 and v[0] in [ '\x80', '\x81', '\x82', '\xff' ]: flags.append('DUK_HSTRING_FLAG_SYMBOL') if len(v) >= 1 and v[0] in [ '\x82', '\xff' ]: flags.append('DUK_HSTRING_FLAG_HIDDEN') if v in [ 'eval', 'arguments' ]: flags.append('DUK_HSTRING_FLAG_EVAL_OR_ARGUMENTS') if reserved_words.has_key(v): flags.append('DUK_HSTRING_FLAG_RESERVED_WORD') if strict_reserved_words.has_key(v): flags.append('DUK_HSTRING_FLAG_STRICT_RESERVED_WORD') h_next = 'NULL' if romstr_next.has_key(v): h_next = '&' + bi_str_map[romstr_next[v]] tmp += 'DUK__STRINIT(%s,%d,%s,%s,%d,%d,%s),' % \ ('|'.join(flags), 1, rom_get_strhash32_macro(v), \ rom_get_strhash16_macro(v), blen, clen, h_next) tmpbytes = [] for c in v: if ord(c) < 128: tmpbytes.append('%d' % ord(c)) else: tmpbytes.append('%dU' % ord(c)) tmpbytes.append('%d' % 0) # NUL term tmp += '{' + ','.join(tmpbytes) + '}' tmp += '};' genc.emitLine(tmp) # Emit the ROM string lookup table used by string interning. # # cdecl> explain const int * const foo; # declare foo as const pointer to const int genc.emitLine('') genc.emitLine('DUK_INTERNAL const duk_hstring * const duk_rom_strings_lookup[%d] = {'% len(romstr_hash)) tmp = [] linecount = 0 for lst in romstr_hash: if len(lst) == 0: genc.emitLine('\tNULL,') else: genc.emitLine('\t(const duk_hstring *) &%s,' % bi_str_map[lst[0]]) genc.emitLine('};') # Emit an array of duk_hstring pointers indexed using DUK_STRIDX_xxx. # This will back e.g. DUK_HTHREAD_STRING_XYZ(thr) directly, without # needing an explicit array in thr/heap->strs[]. # # cdecl > explain const int * const foo; # declare foo as const pointer to const int genc.emitLine('') genc.emitLine('DUK_INTERNAL const duk_hstring * const duk_rom_strings_stridx[%d] = {' % len(strs_needing_stridx)) for s in strs_needing_stridx: genc.emitLine('\t(const duk_hstring *) &%s,' % bi_str_map[s['str']]) # strs_needing_stridx is a list of objects, not plain strings genc.emitLine('};') return bi_str_map # Emit ROM strings header. def rom_emit_strings_header(genc, meta): genc.emitLine('#if !defined(DUK_SINGLE_FILE)') # C++ static const workaround genc.emitLine('DUK_INTERNAL_DECL const duk_hstring * const duk_rom_strings_lookup[%d];' % ROMSTR_LOOKUP_SIZE) genc.emitLine('DUK_INTERNAL_DECL const duk_hstring * const duk_rom_strings_stridx[%d];' % len(meta['strings_stridx'])) genc.emitLine('#endif') # Emit ROM objects initialized types and macros. def rom_emit_object_initializer_types_and_macros(genc): # Objects and functions are straightforward because they just use the # RAM structure which has no dynamic or variable size parts. genc.emitLine('typedef struct duk_romobj duk_romobj; ' + \ 'struct duk_romobj { duk_hobject hdr; };') genc.emitLine('typedef struct duk_romarr duk_romarr; ' + \ 'struct duk_romarr { duk_harray hdr; };') genc.emitLine('typedef struct duk_romfun duk_romfun; ' + \ 'struct duk_romfun { duk_hnatfunc hdr; };') genc.emitLine('typedef struct duk_romobjenv duk_romobjenv; ' + \ 'struct duk_romobjenv { duk_hobjenv hdr; };') # For ROM pointer compression we'd need a -compile time- variant. # The current portable solution is to just assign running numbers # to ROM compressed pointers, and provide the table for user pointer # compression function. Much better solutions would be possible, # but such solutions are often compiler/platform specific. # Emit object/function initializer which is aware of options affecting # the header. Heap next/prev pointers are always NULL. genc.emitLine('#if defined(DUK_USE_HEAPPTR16)') genc.emitLine('#if !defined(DUK_USE_REFCOUNT16) || defined(DUK_USE_HOBJECT_HASH_PART)') genc.emitLine('#error currently assumes DUK_USE_HEAPPTR16 and DUK_USE_REFCOUNT16 are both defined and DUK_USE_HOBJECT_HASH_PART is undefined') genc.emitLine('#endif') #genc.emitLine('#if !defined(DUK_USE_HEAPPTR_ENC16_STATIC)') #genc.emitLine('#error need DUK_USE_HEAPPTR_ENC16_STATIC which provides compile-time pointer compression') #genc.emitLine('#endif') genc.emitLine('#define DUK__ROMOBJ_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize) \\') genc.emitLine('\t{ { { (heaphdr_flags), DUK__REFCINIT((refcount)), 0, 0, (props_enc16) }, (iproto_enc16), (esize), (enext), (asize) } }') genc.emitLine('#define DUK__ROMARR_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize,length) \\') genc.emitLine('\t{ { { { (heaphdr_flags), DUK__REFCINIT((refcount)), 0, 0, (props_enc16) }, (iproto_enc16), (esize), (enext), (asize) }, (length), 0 /*length_nonwritable*/ } }') genc.emitLine('#define DUK__ROMFUN_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize,nativefunc,nargs,magic) \\') genc.emitLine('\t{ { { { (heaphdr_flags), DUK__REFCINIT((refcount)), 0, 0, (props_enc16) }, (iproto_enc16), (esize), (enext), (asize) }, (nativefunc), (duk_int16_t) (nargs), (duk_int16_t) (magic) } }') genc.emitLine('#define DUK__ROMOBJENV_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize,target,has_this) \\') genc.emitLine('\t{ { { { (heaphdr_flags), DUK__REFCINIT((refcount)), 0, 0, (props_enc16) }, (iproto_enc16), (esize), (enext), (asize) }, (duk_hobject *) DUK_LOSE_CONST(target), (has_this) } }') genc.emitLine('#else /* DUK_USE_HEAPPTR16 */') genc.emitLine('#define DUK__ROMOBJ_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize) \\') genc.emitLine('\t{ { { (heaphdr_flags), DUK__REFCINIT((refcount)), NULL, NULL }, (duk_uint8_t *) DUK_LOSE_CONST(props), (duk_hobject *) DUK_LOSE_CONST(iproto), (esize), (enext), (asize), (hsize) } }') genc.emitLine('#define DUK__ROMARR_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize,length) \\') genc.emitLine('\t{ { { { (heaphdr_flags), DUK__REFCINIT((refcount)), NULL, NULL }, (duk_uint8_t *) DUK_LOSE_CONST(props), (duk_hobject *) DUK_LOSE_CONST(iproto), (esize), (enext), (asize), (hsize) }, (length), 0 /*length_nonwritable*/ } }') genc.emitLine('#define DUK__ROMFUN_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize,nativefunc,nargs,magic) \\') genc.emitLine('\t{ { { { (heaphdr_flags), DUK__REFCINIT((refcount)), NULL, NULL }, (duk_uint8_t *) DUK_LOSE_CONST(props), (duk_hobject *) DUK_LOSE_CONST(iproto), (esize), (enext), (asize), (hsize) }, (nativefunc), (duk_int16_t) (nargs), (duk_int16_t) (magic) } }') genc.emitLine('#define DUK__ROMOBJENV_INIT(heaphdr_flags,refcount,props,props_enc16,iproto,iproto_enc16,esize,enext,asize,hsize,target,has_this) \\') genc.emitLine('\t{ { { { (heaphdr_flags), DUK__REFCINIT((refcount)), NULL, NULL }, (duk_uint8_t *) DUK_LOSE_CONST(props), (duk_hobject *) DUK_LOSE_CONST(iproto), (esize), (enext), (asize), (hsize) }, (duk_hobject *) DUK_LOSE_CONST(target), (has_this) } }') genc.emitLine('#endif /* DUK_USE_HEAPPTR16 */') # Initializer typedef for a dummy function pointer. ROM support assumes # function pointers are 32 bits. Using a dummy function pointer type # avoids function pointer to normal pointer cast which emits warnings. genc.emitLine('typedef void (*duk_rom_funcptr)(void);') # Emit duk_tval structs. This gets a bit messier with packed/unpacked # duk_tval, endianness variants, pointer sizes, etc. genc.emitLine('#if defined(DUK_USE_PACKED_TVAL)') genc.emitLine('typedef struct duk_rom_tval_undefined duk_rom_tval_undefined;') genc.emitLine('typedef struct duk_rom_tval_null duk_rom_tval_null;') genc.emitLine('typedef struct duk_rom_tval_lightfunc duk_rom_tval_lightfunc;') genc.emitLine('typedef struct duk_rom_tval_boolean duk_rom_tval_boolean;') genc.emitLine('typedef struct duk_rom_tval_number duk_rom_tval_number;') genc.emitLine('typedef struct duk_rom_tval_object duk_rom_tval_object;') genc.emitLine('typedef struct duk_rom_tval_string duk_rom_tval_string;') genc.emitLine('typedef struct duk_rom_tval_accessor duk_rom_tval_accessor;') genc.emitLine('struct duk_rom_tval_number { duk_uint8_t bytes[8]; };') genc.emitLine('struct duk_rom_tval_accessor { const duk_hobject *get; const duk_hobject *set; };') genc.emitLine('#if defined(DUK_USE_DOUBLE_LE)') genc.emitLine('struct duk_rom_tval_object { const void *ptr; duk_uint32_t hiword; };') genc.emitLine('struct duk_rom_tval_string { const void *ptr; duk_uint32_t hiword; };') genc.emitLine('struct duk_rom_tval_undefined { const void *ptr; duk_uint32_t hiword; };') genc.emitLine('struct duk_rom_tval_null { const void *ptr; duk_uint32_t hiword; };') genc.emitLine('struct duk_rom_tval_lightfunc { duk_rom_funcptr ptr; duk_uint32_t hiword; };') genc.emitLine('struct duk_rom_tval_boolean { duk_uint32_t dummy; duk_uint32_t hiword; };') genc.emitLine('#elif defined(DUK_USE_DOUBLE_BE)') genc.emitLine('struct duk_rom_tval_object { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_string { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_undefined { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_null { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_lightfunc { duk_uint32_t hiword; duk_rom_funcptr ptr; };') genc.emitLine('struct duk_rom_tval_boolean { duk_uint32_t hiword; duk_uint32_t dummy; };') genc.emitLine('#elif defined(DUK_USE_DOUBLE_ME)') genc.emitLine('struct duk_rom_tval_object { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_string { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_undefined { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_null { duk_uint32_t hiword; const void *ptr; };') genc.emitLine('struct duk_rom_tval_lightfunc { duk_uint32_t hiword; duk_rom_funcptr ptr; };') genc.emitLine('struct duk_rom_tval_boolean { duk_uint32_t hiword; duk_uint32_t dummy; };') genc.emitLine('#else') genc.emitLine('#error invalid endianness defines') genc.emitLine('#endif') genc.emitLine('#else /* DUK_USE_PACKED_TVAL */') # Unpacked initializers are written assuming normal struct alignment # rules so that sizeof(duk_tval) == 16. 32-bit pointers need special # handling to ensure the individual initializers pad to 16 bytes as # necessary. # XXX: 32-bit unpacked duk_tval is not yet supported. genc.emitLine('#if defined(DUK_UINTPTR_MAX)') genc.emitLine('#if (DUK_UINTPTR_MAX <= 0xffffffffUL)') genc.emitLine('#error ROM initializer with unpacked duk_tval does not currently work on 32-bit targets') genc.emitLine('#endif') genc.emitLine('#endif') genc.emitLine('typedef struct duk_rom_tval_undefined duk_rom_tval_undefined;') genc.emitLine('struct duk_rom_tval_undefined { duk_small_uint_t tag; duk_small_uint_t extra; duk_uint8_t bytes[8]; };') genc.emitLine('typedef struct duk_rom_tval_null duk_rom_tval_null;') genc.emitLine('struct duk_rom_tval_null { duk_small_uint_t tag; duk_small_uint_t extra; duk_uint8_t bytes[8]; };') genc.emitLine('typedef struct duk_rom_tval_boolean duk_rom_tval_boolean;') genc.emitLine('struct duk_rom_tval_boolean { duk_small_uint_t tag; duk_small_uint_t extra; duk_uint32_t val; duk_uint32_t unused; };') genc.emitLine('typedef struct duk_rom_tval_number duk_rom_tval_number;') genc.emitLine('struct duk_rom_tval_number { duk_small_uint_t tag; duk_small_uint_t extra; duk_uint8_t bytes[8]; };') genc.emitLine('typedef struct duk_rom_tval_object duk_rom_tval_object;') genc.emitLine('struct duk_rom_tval_object { duk_small_uint_t tag; duk_small_uint_t extra; const duk_heaphdr *val; };') genc.emitLine('typedef struct duk_rom_tval_string duk_rom_tval_string;') genc.emitLine('struct duk_rom_tval_string { duk_small_uint_t tag; duk_small_uint_t extra; const duk_heaphdr *val; };') genc.emitLine('typedef struct duk_rom_tval_lightfunc duk_rom_tval_lightfunc;') genc.emitLine('struct duk_rom_tval_lightfunc { duk_small_uint_t tag; duk_small_uint_t extra; duk_rom_funcptr ptr; };') genc.emitLine('typedef struct duk_rom_tval_accessor duk_rom_tval_accessor;') genc.emitLine('struct duk_rom_tval_accessor { const duk_hobject *get; const duk_hobject *set; };') genc.emitLine('#endif /* DUK_USE_PACKED_TVAL */') genc.emitLine('') # Double initializer byte shuffle macro to handle byte orders # without duplicating the entire initializers. genc.emitLine('#if defined(DUK_USE_DOUBLE_LE)') genc.emitLine('#define DUK__DBLBYTES(a,b,c,d,e,f,g,h) { (h), (g), (f), (e), (d), (c), (b), (a) }') genc.emitLine('#elif defined(DUK_USE_DOUBLE_BE)') genc.emitLine('#define DUK__DBLBYTES(a,b,c,d,e,f,g,h) { (a), (b), (c), (d), (e), (f), (g), (h) }') genc.emitLine('#elif defined(DUK_USE_DOUBLE_ME)') genc.emitLine('#define DUK__DBLBYTES(a,b,c,d,e,f,g,h) { (d), (c), (b), (a), (h), (g), (f), (e) }') genc.emitLine('#else') genc.emitLine('#error invalid endianness defines') genc.emitLine('#endif') genc.emitLine('') # Emit duk_tval initializer literal macros. genc.emitLine('#if defined(DUK_USE_PACKED_TVAL)') genc.emitLine('#define DUK__TVAL_NUMBER(hostbytes) { hostbytes }') # bytes already in host order genc.emitLine('#if defined(DUK_USE_DOUBLE_LE)') genc.emitLine('#define DUK__TVAL_UNDEFINED() { (const void *) NULL, (DUK_TAG_UNDEFINED << 16) }') genc.emitLine('#define DUK__TVAL_NULL() { (const void *) NULL, (DUK_TAG_NULL << 16) }') genc.emitLine('#define DUK__TVAL_LIGHTFUNC(func,flags) { (duk_rom_funcptr) (func), (DUK_TAG_LIGHTFUNC << 16) + (flags) }') genc.emitLine('#define DUK__TVAL_BOOLEAN(bval) { 0, (DUK_TAG_BOOLEAN << 16) + (bval) }') genc.emitLine('#define DUK__TVAL_OBJECT(ptr) { (const void *) (ptr), (DUK_TAG_OBJECT << 16) }') genc.emitLine('#define DUK__TVAL_STRING(ptr) { (const void *) (ptr), (DUK_TAG_STRING << 16) }') genc.emitLine('#elif defined(DUK_USE_DOUBLE_BE)') genc.emitLine('#define DUK__TVAL_UNDEFINED() { (DUK_TAG_UNDEFINED << 16), (const void *) NULL }') genc.emitLine('#define DUK__TVAL_NULL() { (DUK_TAG_NULL << 16), (const void *) NULL }') genc.emitLine('#define DUK__TVAL_LIGHTFUNC(func,flags) { (DUK_TAG_LIGHTFUNC << 16) + (flags), (duk_rom_funcptr) (func) }') genc.emitLine('#define DUK__TVAL_BOOLEAN(bval) { (DUK_TAG_BOOLEAN << 16) + (bval), 0 }') genc.emitLine('#define DUK__TVAL_OBJECT(ptr) { (DUK_TAG_OBJECT << 16), (const void *) (ptr) }') genc.emitLine('#define DUK__TVAL_STRING(ptr) { (DUK_TAG_STRING << 16), (const void *) (ptr) }') genc.emitLine('#elif defined(DUK_USE_DOUBLE_ME)') genc.emitLine('#define DUK__TVAL_UNDEFINED() { (DUK_TAG_UNDEFINED << 16), (const void *) NULL }') genc.emitLine('#define DUK__TVAL_NULL() { (DUK_TAG_NULL << 16), (const void *) NULL }') genc.emitLine('#define DUK__TVAL_LIGHTFUNC(func,flags) { (DUK_TAG_LIGHTFUNC << 16) + (flags), (duk_rom_funcptr) (func) }') genc.emitLine('#define DUK__TVAL_BOOLEAN(bval) { (DUK_TAG_BOOLEAN << 16) + (bval), 0 }') genc.emitLine('#define DUK__TVAL_OBJECT(ptr) { (DUK_TAG_OBJECT << 16), (const void *) (ptr) }') genc.emitLine('#define DUK__TVAL_STRING(ptr) { (DUK_TAG_STRING << 16), (const void *) (ptr) }') genc.emitLine('#else') genc.emitLine('#error invalid endianness defines') genc.emitLine('#endif') genc.emitLine('#else /* DUK_USE_PACKED_TVAL */') genc.emitLine('#define DUK__TVAL_NUMBER(hostbytes) { DUK_TAG_NUMBER, 0, hostbytes }') # bytes already in host order genc.emitLine('#define DUK__TVAL_UNDEFINED() { DUK_TAG_UNDEFINED, 0, {0,0,0,0,0,0,0,0} }') genc.emitLine('#define DUK__TVAL_NULL() { DUK_TAG_NULL, 0, {0,0,0,0,0,0,0,0} }') genc.emitLine('#define DUK__TVAL_BOOLEAN(bval) { DUK_TAG_BOOLEAN, 0, (bval), 0 }') genc.emitLine('#define DUK__TVAL_OBJECT(ptr) { DUK_TAG_OBJECT, 0, (const duk_heaphdr *) (ptr) }') genc.emitLine('#define DUK__TVAL_STRING(ptr) { DUK_TAG_STRING, 0, (const duk_heaphdr *) (ptr) }') genc.emitLine('#define DUK__TVAL_LIGHTFUNC(func,flags) { DUK_TAG_LIGHTFUNC, (flags), (duk_rom_funcptr) (func) }') genc.emitLine('#endif /* DUK_USE_PACKED_TVAL */') genc.emitLine('#define DUK__TVAL_ACCESSOR(getter,setter) { (const duk_hobject *) (getter), (const duk_hobject *) (setter) }') # Emit ROM objects source: the object/function headers themselves, property # table structs for different property table sizes/types, and property table # initializers. def rom_emit_objects(genc, meta, bi_str_map): objs = meta['objects'] id_to_bidx = meta['_objid_to_bidx'] # Table for compressed ROM pointers; reserve high range of compressed pointer # values for this purpose. This must contain all ROM pointers that might be # referenced (all objects, strings, and property tables at least). romptr_compress_list = [] def compress_rom_ptr(x): if x == 'NULL': return 0 try: idx = romptr_compress_list.index(x) res = ROMPTR_FIRST + idx except ValueError: romptr_compress_list.append(x) res = ROMPTR_FIRST + len(romptr_compress_list) - 1 assert(res <= 0xffff) return res # Need string and object maps (id -> C symbol name) early. bi_obj_map = {} # object id -> initializer variable name for idx,obj in enumerate(objs): bi_obj_map[obj['id']] = 'duk_obj_%d' % idx # Add built-in strings and objects to compressed ROM pointers first. for k in sorted(bi_str_map.keys()): compress_rom_ptr('&%s' % bi_str_map[k]) for k in sorted(bi_obj_map.keys()): compress_rom_ptr('&%s' % bi_obj_map[k]) # Property attributes lookup, map metadata attribute string into a # C initializer. attr_lookup = { '': 'DUK_PROPDESC_FLAGS_NONE', 'w': 'DUK_PROPDESC_FLAGS_W', 'e': 'DUK_PROPDESC_FLAGS_E', 'c': 'DUK_PROPDESC_FLAGS_C', 'we': 'DUK_PROPDESC_FLAGS_WE', 'wc': 'DUK_PROPDESC_FLAGS_WC', 'ec': 'DUK_PROPDESC_FLAGS_EC', 'wec': 'DUK_PROPDESC_FLAGS_WEC', 'a': 'DUK_PROPDESC_FLAGS_NONE|DUK_PROPDESC_FLAG_ACCESSOR', 'ea': 'DUK_PROPDESC_FLAGS_E|DUK_PROPDESC_FLAG_ACCESSOR', 'ca': 'DUK_PROPDESC_FLAGS_C|DUK_PROPDESC_FLAG_ACCESSOR', 'eca': 'DUK_PROPDESC_FLAGS_EC|DUK_PROPDESC_FLAG_ACCESSOR', } # Emit property table structs. These are very complex because # property count *and* individual property type affect the fields # in the initializer, properties can be data properties or accessor # properties or different duk_tval types. There are also several # property table memory layouts, each with a different ordering of # keys, values, etc. Union initializers would make things a bit # easier but they're not very portable (being C99). # # The easy solution is to use a separate initializer type for each # property type. Could also cache and reuse identical initializers # but there'd be very few of them so it's more straightforward to # not reuse the structs. # # NOTE: naming is a bit inconsistent here, duk_tval is used also # to refer to property value initializers like a getter/setter pair. genc.emitLine('#if defined(DUK_USE_HOBJECT_LAYOUT_1)') for idx,obj in enumerate(objs): numprops = len(obj['properties']) if numprops == 0: continue tmp = 'typedef struct duk_romprops_%d duk_romprops_%d; ' % (idx, idx) tmp += 'struct duk_romprops_%d { ' % idx for idx,val in enumerate(obj['properties']): tmp += 'const duk_hstring *key%d; ' % idx for idx,val in enumerate(obj['properties']): # XXX: fastint support tmp += '%s val%d; ' % (rom_get_value_initializer_type(meta, val, bi_str_map, bi_obj_map), idx) for idx,val in enumerate(obj['properties']): tmp += 'duk_uint8_t flags%d; ' % idx tmp += '};' genc.emitLine(tmp) genc.emitLine('#elif defined(DUK_USE_HOBJECT_LAYOUT_2)') for idx,obj in enumerate(objs): numprops = len(obj['properties']) if numprops == 0: continue tmp = 'typedef struct duk_romprops_%d duk_romprops_%d; ' % (idx, idx) tmp += 'struct duk_romprops_%d { ' % idx for idx,val in enumerate(obj['properties']): # XXX: fastint support tmp += '%s val%d; ' % (rom_get_value_initializer_type(meta, val, bi_str_map, bi_obj_map), idx) for idx,val in enumerate(obj['properties']): tmp += 'const duk_hstring *key%d; ' % idx for idx,val in enumerate(obj['properties']): tmp += 'duk_uint8_t flags%d; ' % idx # Padding follows for flags, but we don't need to emit it # (at the moment there is never an array or hash part). tmp += '};' genc.emitLine(tmp) genc.emitLine('#elif defined(DUK_USE_HOBJECT_LAYOUT_3)') for idx,obj in enumerate(objs): numprops = len(obj['properties']) if numprops == 0: continue tmp = 'typedef struct duk_romprops_%d duk_romprops_%d; ' % (idx, idx) tmp += 'struct duk_romprops_%d { ' % idx for idx,val in enumerate(obj['properties']): # XXX: fastint support tmp += '%s val%d; ' % (rom_get_value_initializer_type(meta, val, bi_str_map, bi_obj_map), idx) # No array values for idx,val in enumerate(obj['properties']): tmp += 'const duk_hstring *key%d; ' % idx # No hash index for idx,val in enumerate(obj['properties']): tmp += 'duk_uint8_t flags%d; ' % idx tmp += '};' genc.emitLine(tmp) genc.emitLine('#else') genc.emitLine('#error invalid object layout') genc.emitLine('#endif') genc.emitLine('') # Forward declare all property tables so that objects can reference them. # Also pointer compress them. for idx,obj in enumerate(objs): numprops = len(obj['properties']) if numprops == 0: continue # We would like to use DUK_INTERNAL_DECL here, but that maps # to "static const" in a single file build which has C++ # portability issues: you can't forward declare a static const. # We can't reorder the property tables to avoid this because # there are cyclic references. So, as the current workaround, # declare as external. genc.emitLine('DUK_EXTERNAL_DECL const duk_romprops_%d duk_prop_%d;' % (idx, idx)) # Add property tables to ROM compressed pointers too. compress_rom_ptr('&duk_prop_%d' % idx) genc.emitLine('') # Forward declare all objects so that objects can reference them, # e.g. internal prototype reference. for idx,obj in enumerate(objs): # Careful with C++: must avoid redefining a non-extern const. # See commentary above for duk_prop_%d forward declarations. if obj.get('callable', False): genc.emitLine('DUK_EXTERNAL_DECL const duk_romfun duk_obj_%d;' % idx) elif obj.get('class') == 'Array': genc.emitLine('DUK_EXTERNAL_DECL const duk_romarr duk_obj_%d;' % idx) elif obj.get('class') == 'ObjEnv': genc.emitLine('DUK_EXTERNAL_DECL const duk_romobjenv duk_obj_%d;' % idx) else: genc.emitLine('DUK_EXTERNAL_DECL const duk_romobj duk_obj_%d;' % idx) genc.emitLine('') # Define objects, reference property tables. Objects will be # logically non-extensible so also leave their extensible flag # cleared despite what metadata requests; the runtime code expects # ROM objects to be non-extensible. for idx,obj in enumerate(objs): numprops = len(obj['properties']) isfunc = obj.get('callable', False) if isfunc: tmp = 'DUK_EXTERNAL const duk_romfun duk_obj_%d = ' % idx elif obj.get('class') == 'Array': tmp = 'DUK_EXTERNAL const duk_romarr duk_obj_%d = ' % idx elif obj.get('class') == 'ObjEnv': tmp = 'DUK_EXTERNAL const duk_romobjenv duk_obj_%d = ' % idx else: tmp = 'DUK_EXTERNAL const duk_romobj duk_obj_%d = ' % idx flags = [ 'DUK_HTYPE_OBJECT', 'DUK_HEAPHDR_FLAG_READONLY', 'DUK_HEAPHDR_FLAG_REACHABLE' ] if isfunc: flags.append('DUK_HOBJECT_FLAG_NATFUNC') flags.append('DUK_HOBJECT_FLAG_STRICT') flags.append('DUK_HOBJECT_FLAG_NEWENV') if obj.get('callable', False): flags.append('DUK_HOBJECT_FLAG_CALLABLE') if obj.get('constructable', False): flags.append('DUK_HOBJECT_FLAG_CONSTRUCTABLE') if obj.get('class') == 'Array': flags.append('DUK_HOBJECT_FLAG_EXOTIC_ARRAY') if obj.get('special_call', False): flags.append('DUK_HOBJECT_FLAG_SPECIAL_CALL') flags.append('DUK_HOBJECT_CLASS_AS_FLAGS(%d)' % class_to_number(obj['class'])) # XXX: use constant, not number refcount = 1 # refcount is faked to be always 1 if numprops == 0: props = 'NULL' else: props = '&duk_prop_%d' % idx props_enc16 = compress_rom_ptr(props) if obj.has_key('internal_prototype'): iproto = '&%s' % bi_obj_map[obj['internal_prototype']] else: iproto = 'NULL' iproto_enc16 = compress_rom_ptr(iproto) e_size = numprops e_next = e_size a_size = 0 # never an array part for now h_size = 0 # never a hash for now; not appropriate for perf relevant builds if isfunc: nativefunc = obj['native'] if obj.get('varargs', False): nargs = 'DUK_VARARGS' elif obj.has_key('nargs'): nargs = '%d' % obj['nargs'] else: assert(False) # 'nargs' should be defaulted from 'length' at metadata load magic = '%d' % resolve_magic(obj.get('magic', None), id_to_bidx) else: nativefunc = 'dummy' nargs = '0' magic = '0' assert(a_size == 0) assert(h_size == 0) if isfunc: tmp += 'DUK__ROMFUN_INIT(%s,%d,%s,%d,%s,%d,%d,%d,%d,%d,%s,%s,%s);' % \ ('|'.join(flags), refcount, props, props_enc16, \ iproto, iproto_enc16, e_size, e_next, a_size, h_size, \ nativefunc, nargs, magic) elif obj.get('class') == 'Array': arrlen = 0 tmp += 'DUK__ROMARR_INIT(%s,%d,%s,%d,%s,%d,%d,%d,%d,%d,%d);' % \ ('|'.join(flags), refcount, props, props_enc16, \ iproto, iproto_enc16, e_size, e_next, a_size, h_size, arrlen) elif obj.get('class') == 'ObjEnv': objenv_target = '&%s' % bi_obj_map[obj['objenv_target']] objenv_has_this = obj['objenv_has_this'] tmp += 'DUK__ROMOBJENV_INIT(%s,%d,%s,%d,%s,%d,%d,%d,%d,%d,%s,%d);' % \ ('|'.join(flags), refcount, props, props_enc16, \ iproto, iproto_enc16, e_size, e_next, a_size, h_size, objenv_target, objenv_has_this) else: tmp += 'DUK__ROMOBJ_INIT(%s,%d,%s,%d,%s,%d,%d,%d,%d,%d);' % \ ('|'.join(flags), refcount, props, props_enc16, \ iproto, iproto_enc16, e_size, e_next, a_size, h_size) genc.emitLine(tmp) # Property tables. Can reference arbitrary strings and objects as # they're defined before them. # Properties will be non-configurable, but must be writable so that # standard property semantics allow shadowing properties to be # established in inherited objects (e.g. "var obj={}; obj.toString # = myToString"). Enumerable can also be kept. def _prepAttrs(val): attrs = val['attributes'] assert('c' not in attrs) return attr_lookup[attrs] def _emitPropTableInitializer(idx, obj, layout): init_vals = [] init_keys = [] init_flags = [] numprops = len(obj['properties']) for val in obj['properties']: init_keys.append('(const duk_hstring *)&%s' % bi_str_map[val['key']]) for val in obj['properties']: # XXX: fastint support init_vals.append('%s' % rom_get_value_initializer_literal(meta, val, bi_str_map, bi_obj_map)) for val in obj['properties']: init_flags.append('%s' % _prepAttrs(val)) if layout == 1: initlist = init_keys + init_vals + init_flags elif layout == 2: initlist = init_vals + init_keys + init_flags elif layout == 3: # Same as layout 2 now, no hash/array initlist = init_vals + init_keys + init_flags if len(initlist) > 0: genc.emitLine('DUK_EXTERNAL const duk_romprops_%d duk_prop_%d = {%s};' % (idx, idx, ','.join(initlist))) genc.emitLine('#if defined(DUK_USE_HOBJECT_LAYOUT_1)') for idx,obj in enumerate(objs): _emitPropTableInitializer(idx, obj, 1) genc.emitLine('#elif defined(DUK_USE_HOBJECT_LAYOUT_2)') for idx,obj in enumerate(objs): _emitPropTableInitializer(idx, obj, 2) genc.emitLine('#elif defined(DUK_USE_HOBJECT_LAYOUT_3)') for idx,obj in enumerate(objs): _emitPropTableInitializer(idx, obj, 3) genc.emitLine('#else') genc.emitLine('#error invalid object layout') genc.emitLine('#endif') genc.emitLine('') # Emit a list of ROM builtins (those objects needing a bidx). # # cdecl > explain const int * const foo; # declare foo as const pointer to const int count_bidx = 0 for bi in objs: if bi.get('bidx_used', False): count_bidx += 1 genc.emitLine('DUK_INTERNAL const duk_hobject * const duk_rom_builtins_bidx[%d] = {' % count_bidx) for bi in objs: if not bi.get('bidx_used', False): continue # for this we want the toplevel objects only genc.emitLine('\t(const duk_hobject *) &%s,' % bi_obj_map[bi['id']]) genc.emitLine('};') # Emit a table of compressed ROM pointers. We must be able to # compress ROM pointers at compile time so we assign running # indices to them. User pointer compression macros must use this # array to encode/decode ROM pointers. genc.emitLine('') genc.emitLine('#if defined(DUK_USE_ROM_OBJECTS) && defined(DUK_USE_HEAPPTR16)') genc.emitLine('DUK_EXTERNAL const void * const duk_rom_compressed_pointers[%d] = {' % (len(romptr_compress_list) + 1)) for idx,ptr in enumerate(romptr_compress_list): genc.emitLine('\t(const void *) %s, /* 0x%04x */' % (ptr, ROMPTR_FIRST + idx)) romptr_highest = ROMPTR_FIRST + len(romptr_compress_list) - 1 genc.emitLine('\tNULL') # for convenience genc.emitLine('};') genc.emitLine('#endif') logger.debug('%d compressed rom pointers (used range is [0x%04x,0x%04x], %d space left)' % \ (len(romptr_compress_list), ROMPTR_FIRST, romptr_highest, 0xffff - romptr_highest)) # Undefine helpers. genc.emitLine('') for i in [ 'DUK__STRHASH16', 'DUK__STRHASH32', 'DUK__DBLBYTES', 'DUK__TVAL_NUMBER', 'DUK__TVAL_UNDEFINED', 'DUK__TVAL_NULL', 'DUK__TVAL_BOOLEAN', 'DUK__TVAL_OBJECT', 'DUK__TVAL_STRING', 'DUK__STRINIT', 'DUK__ROMOBJ_INIT', 'DUK__ROMFUN_INIT' ]: genc.emitLine('#undef ' + i) return romptr_compress_list # Emit ROM objects header. def rom_emit_objects_header(genc, meta): bidx = 0 for bi in meta['objects']: if not bi.get('bidx_used', False): continue # for this we want the toplevel objects only genc.emitDefine('DUK_BIDX_' + '_'.join(bi['id'].upper().split('_')[1:]), bidx) # bi_foo_bar -> FOO_BAR bidx += 1 count_bidx = bidx genc.emitDefine('DUK_NUM_BUILTINS', count_bidx) genc.emitDefine('DUK_NUM_BIDX_BUILTINS', count_bidx) genc.emitDefine('DUK_NUM_ALL_BUILTINS', len(meta['objects'])) genc.emitLine('') genc.emitLine('#if !defined(DUK_SINGLE_FILE)') # C++ static const workaround genc.emitLine('DUK_INTERNAL_DECL const duk_hobject * const duk_rom_builtins_bidx[%d];' % count_bidx) genc.emitLine('#endif') # XXX: missing declarations here, not an issue for single source build. # Add missing declarations. # XXX: For example, 'DUK_EXTERNAL_DECL ... duk_rom_compressed_pointers[]' is missing. # # Shared for both RAM and ROM # def emit_header_native_function_declarations(genc, meta): emitted = {} # To suppress duplicates funclist = [] def _emit(fname): if not emitted.has_key(fname): emitted[fname] = True funclist.append(fname) for o in meta['objects']: if o.has_key('native'): _emit(o['native']) for p in o['properties']: v = p['value'] if isinstance(v, dict) and v['type'] == 'lightfunc': assert(v.has_key('native')) _emit(v['native']) logger.debug('Lightfunc function declaration: %r' % v['native']) for fname in funclist: # Visibility depends on whether the function is Duktape internal or user. # Use a simple prefix for now. if fname[:4] == 'duk_': genc.emitLine('DUK_INTERNAL_DECL duk_ret_t %s(duk_context *ctx);' % fname) else: genc.emitLine('extern duk_ret_t %s(duk_context *ctx);' % fname) # # Main # def main(): parser = optparse.OptionParser() parser.add_option('--git-commit', dest='git_commit', default=None, help='Git commit hash') parser.add_option('--git-describe', dest='git_describe', default=None, help='Git describe') parser.add_option('--git-branch', dest='git_branch', default=None, help='Git branch name') parser.add_option('--duk-version', dest='duk_version', default=None, help='Duktape version (e.g. 10203)') parser.add_option('--quiet', dest='quiet', action='store_true', default=False, help='Suppress info messages (show warnings)') parser.add_option('--verbose', dest='verbose', action='store_true', default=False, help='Show verbose debug messages') parser.add_option('--used-stridx-metadata', dest='used_stridx_metadata', help='DUK_STRIDX_xxx used by source/headers, JSON format') parser.add_option('--strings-metadata', dest='strings_metadata', help='Default built-in strings metadata file, YAML format') parser.add_option('--objects-metadata', dest='objects_metadata', help='Default built-in objects metadata file, YAML format') parser.add_option('--active-options', dest='active_options', help='Active config options from genconfig.py, JSON format') parser.add_option('--user-builtin-metadata', dest='obsolete_builtin_metadata', default=None, help=optparse.SUPPRESS_HELP) parser.add_option('--builtin-file', dest='builtin_files', metavar='FILENAME', action='append', default=[], help='Built-in string/object YAML metadata to be applied over default built-ins (multiple files may be given, applied in sequence)') parser.add_option('--ram-support', dest='ram_support', action='store_true', default=False, help='Support RAM strings/objects') parser.add_option('--rom-support', dest='rom_support', action='store_true', default=False, help='Support ROM strings/objects (increases output size considerably)') parser.add_option('--rom-auto-lightfunc', dest='rom_auto_lightfunc', action='store_true', default=False, help='Convert ROM built-in function properties into lightfuncs automatically whenever possible') parser.add_option('--out-header', dest='out_header', help='Output header file') parser.add_option('--out-source', dest='out_source', help='Output source file') parser.add_option('--out-metadata-json', dest='out_metadata_json', help='Output metadata file') parser.add_option('--dev-dump-final-ram-metadata', dest='dev_dump_final_ram_metadata', help='Development option') parser.add_option('--dev-dump-final-rom-metadata', dest='dev_dump_final_rom_metadata', help='Development option') (opts, args) = parser.parse_args() if opts.obsolete_builtin_metadata is not None: raise Exception('--user-builtin-metadata has been removed, use --builtin-file instead') # Log level. if opts.quiet: logger.setLevel(logging.WARNING) elif opts.verbose: logger.setLevel(logging.DEBUG) # Options processing. build_info = { 'git_commit': opts.git_commit, 'git_branch': opts.git_branch, 'git_describe': opts.git_describe, 'duk_version': int(opts.duk_version), } desc = [] if opts.ram_support: desc += [ 'ram built-in support' ] if opts.rom_support: desc += [ 'rom built-in support' ] if opts.rom_auto_lightfunc: desc += [ 'rom auto lightfunc' ] logger.info('Creating built-in initialization data: ' + ', '.join(desc)) # Read in metadata files, normalizing and merging as necessary. active_opts = {} if opts.active_options is not None: with open(opts.active_options, 'rb') as f: active_opts = json.loads(f.read()) ram_meta = load_metadata(opts, rom=False, build_info=build_info, active_opts=active_opts) rom_meta = load_metadata(opts, rom=True, build_info=build_info, active_opts=active_opts) if opts.dev_dump_final_ram_metadata is not None: dump_metadata(ram_meta, opts.dev_dump_final_ram_metadata) if opts.dev_dump_final_rom_metadata is not None: dump_metadata(rom_meta, opts.dev_dump_final_rom_metadata) # Create RAM init data bitstreams. ramstr_data, ramstr_maxlen = gen_ramstr_initdata_bitpacked(ram_meta) ram_native_funcs, ram_natfunc_name_to_natidx = get_ramobj_native_func_maps(ram_meta) if opts.ram_support: ramobj_data_le = gen_ramobj_initdata_bitpacked(ram_meta, ram_native_funcs, ram_natfunc_name_to_natidx, 'little') ramobj_data_be = gen_ramobj_initdata_bitpacked(ram_meta, ram_native_funcs, ram_natfunc_name_to_natidx, 'big') ramobj_data_me = gen_ramobj_initdata_bitpacked(ram_meta, ram_native_funcs, ram_natfunc_name_to_natidx, 'mixed') # Write source and header files. gc_src = dukutil.GenerateC() gc_src.emitHeader('genbuiltins.py') gc_src.emitLine('#include "duk_internal.h"') gc_src.emitLine('') gc_src.emitLine('#if defined(DUK_USE_ASSERTIONS)') gc_src.emitLine('#define DUK__REFCINIT(refc) 0 /*h_assert_refcount*/, (refc) /*actual*/') gc_src.emitLine('#else') gc_src.emitLine('#define DUK__REFCINIT(refc) (refc) /*actual*/') gc_src.emitLine('#endif') gc_src.emitLine('') gc_src.emitLine('#if defined(DUK_USE_ROM_STRINGS)') if opts.rom_support: rom_bi_str_map = rom_emit_strings_source(gc_src, rom_meta) rom_emit_object_initializer_types_and_macros(gc_src) rom_emit_objects(gc_src, rom_meta, rom_bi_str_map) else: gc_src.emitLine('#error ROM support not enabled, rerun configure.py with --rom-support') gc_src.emitLine('#else /* DUK_USE_ROM_STRINGS */') emit_ramstr_source_strinit_data(gc_src, ramstr_data) gc_src.emitLine('#endif /* DUK_USE_ROM_STRINGS */') gc_src.emitLine('') gc_src.emitLine('#if defined(DUK_USE_ROM_OBJECTS)') if opts.rom_support: gc_src.emitLine('#if !defined(DUK_USE_ROM_STRINGS)') gc_src.emitLine('#error DUK_USE_ROM_OBJECTS requires DUK_USE_ROM_STRINGS') gc_src.emitLine('#endif') gc_src.emitLine('#if defined(DUK_USE_HSTRING_ARRIDX)') gc_src.emitLine('#error DUK_USE_HSTRING_ARRIDX is currently incompatible with ROM built-ins') gc_src.emitLine('#endif') else: gc_src.emitLine('#error ROM support not enabled, rerun configure.py with --rom-support') gc_src.emitLine('#else /* DUK_USE_ROM_OBJECTS */') if opts.ram_support: emit_ramobj_source_nativefunc_array(gc_src, ram_native_funcs) # endian independent gc_src.emitLine('#if defined(DUK_USE_DOUBLE_LE)') emit_ramobj_source_objinit_data(gc_src, ramobj_data_le) gc_src.emitLine('#elif defined(DUK_USE_DOUBLE_BE)') emit_ramobj_source_objinit_data(gc_src, ramobj_data_be) gc_src.emitLine('#elif defined(DUK_USE_DOUBLE_ME)') emit_ramobj_source_objinit_data(gc_src, ramobj_data_me) gc_src.emitLine('#else') gc_src.emitLine('#error invalid endianness defines') gc_src.emitLine('#endif') else: gc_src.emitLine('#error RAM support not enabled, rerun configure.py with --ram-support') gc_src.emitLine('#endif /* DUK_USE_ROM_OBJECTS */') gc_hdr = dukutil.GenerateC() gc_hdr.emitHeader('genbuiltins.py') gc_hdr.emitLine('#if !defined(DUK_BUILTINS_H_INCLUDED)') gc_hdr.emitLine('#define DUK_BUILTINS_H_INCLUDED') gc_hdr.emitLine('') gc_hdr.emitLine('#if defined(DUK_USE_ROM_STRINGS)') if opts.rom_support: emit_header_stridx_defines(gc_hdr, rom_meta) rom_emit_strings_header(gc_hdr, rom_meta) else: gc_hdr.emitLine('#error ROM support not enabled, rerun configure.py with --rom-support') gc_hdr.emitLine('#else /* DUK_USE_ROM_STRINGS */') if opts.ram_support: emit_header_stridx_defines(gc_hdr, ram_meta) emit_ramstr_header_strinit_defines(gc_hdr, ram_meta, ramstr_data, ramstr_maxlen) else: gc_hdr.emitLine('#error RAM support not enabled, rerun configure.py with --ram-support') gc_hdr.emitLine('#endif /* DUK_USE_ROM_STRINGS */') gc_hdr.emitLine('') gc_hdr.emitLine('#if defined(DUK_USE_ROM_OBJECTS)') if opts.rom_support: # Currently DUK_USE_ROM_PTRCOMP_FIRST must match our fixed # define, and the two must be updated in sync. Catch any # mismatch to avoid difficult to diagnose errors. gc_hdr.emitLine('#if !defined(DUK_USE_ROM_PTRCOMP_FIRST)') gc_hdr.emitLine('#error missing DUK_USE_ROM_PTRCOMP_FIRST define') gc_hdr.emitLine('#endif') gc_hdr.emitLine('#if (DUK_USE_ROM_PTRCOMP_FIRST != %dL)' % ROMPTR_FIRST) gc_hdr.emitLine('#error DUK_USE_ROM_PTRCOMP_FIRST must match ROMPTR_FIRST in genbuiltins.py (%d), update manually and re-dist' % ROMPTR_FIRST) gc_hdr.emitLine('#endif') emit_header_native_function_declarations(gc_hdr, rom_meta) rom_emit_objects_header(gc_hdr, rom_meta) else: gc_hdr.emitLine('#error RAM support not enabled, rerun configure.py with --ram-support') gc_hdr.emitLine('#else /* DUK_USE_ROM_OBJECTS */') if opts.ram_support: emit_header_native_function_declarations(gc_hdr, ram_meta) emit_ramobj_header_nativefunc_array(gc_hdr, ram_native_funcs) emit_ramobj_header_objects(gc_hdr, ram_meta) gc_hdr.emitLine('#if defined(DUK_USE_DOUBLE_LE)') emit_ramobj_header_initdata(gc_hdr, ramobj_data_le) gc_hdr.emitLine('#elif defined(DUK_USE_DOUBLE_BE)') emit_ramobj_header_initdata(gc_hdr, ramobj_data_be) gc_hdr.emitLine('#elif defined(DUK_USE_DOUBLE_ME)') emit_ramobj_header_initdata(gc_hdr, ramobj_data_me) gc_hdr.emitLine('#else') gc_hdr.emitLine('#error invalid endianness defines') gc_hdr.emitLine('#endif') else: gc_hdr.emitLine('#error RAM support not enabled, rerun configure.py with --ram-support') gc_hdr.emitLine('#endif /* DUK_USE_ROM_OBJECTS */') gc_hdr.emitLine('#endif /* DUK_BUILTINS_H_INCLUDED */') with open(opts.out_source, 'wb') as f: f.write(gc_src.getString()) logger.debug('Wrote built-ins source to ' + opts.out_source) with open(opts.out_header, 'wb') as f: f.write(gc_hdr.getString()) logger.debug('Wrote built-ins header to ' + opts.out_header) # Write a JSON file with build metadata, e.g. built-in strings. ver = long(build_info['duk_version']) plain_strs = [] base64_strs = [] str_objs = [] for s in ram_meta['strings_stridx']: # XXX: provide all lists? t1 = bytes_to_unicode(s['str']) t2 = unicode_to_bytes(s['str']).encode('base64').strip() plain_strs.append(t1) base64_strs.append(t2) str_objs.append({ 'plain': t1, 'base64': t2, 'define': s['define'] }) meta = { 'comment': 'Metadata for Duktape sources', 'duk_version': ver, 'duk_version_string': '%d.%d.%d' % (ver / 10000, (ver / 100) % 100, ver % 100), 'git_commit': build_info['git_commit'], 'git_branch': build_info['git_branch'], 'git_describe': build_info['git_describe'], 'builtin_strings': plain_strs, 'builtin_strings_base64': base64_strs, 'builtin_strings_info': str_objs } with open(opts.out_metadata_json, 'wb') as f: f.write(json.dumps(meta, indent=4, sort_keys=True, ensure_ascii=True)) logger.debug('Wrote built-ins metadata to ' + opts.out_metadata_json) if __name__ == '__main__': main()