先日、Pure DataのパッチをhvccでCに変換してNTS-1 mkIIで動かしてみましたが、単なる鋸波のオシレータに29Kbも使っていました。
$ size ./sawosc.nts1mkiiunit
text data bss dec hex filename
28018 964 92 29074 7192 ./sawosc.nts1mkiiunit
最初はそんなものかなと思っていたのですが、Cのコードの量から考えると、もっと小さくなっても良さそうです。削れるものがあれば、フットプリントの制限が32Kbであるディレイユニットやリバーブユニットでも、Pure Dataを使えるかもしれません。
まずは、ファイルサイズの詳しい内訳を見てみました。
$ size -A ./sawosc.nts1mkiiunit
./sawosc.nts1mkiiunit :
section size addr
.hash 2164 212
.dynsym 4416 2376
.dynstr 5766 6792
.rel.plt 712 13440
.text 11824 14152
.plt 1440 25976
.unit_header 816 27416
.preinit_array 0 28232
.init_array 0 28232
.dynamic 152 28232
.got 392 28384
.data 420 28776
.bss 88 29196
.stack 4 29296
.comment 73 0
.ARM.attributes 50 0
.rel.dyn 880 12560
Total 29197
これを見ると、.dynsymや.dynstrなど、動的リンク用のデータが占める割合が結構大きい印象です。この中身を見てみます。
$ readelf -p .dynstr ./sawosc.nts1mkiiunit
String dump of section '.dynstr':
[ 1] unit_header
[ d] hLp_init
[ 16] malloc
[ 1d] hLp_free
[ 26] hLp_hasData
[ 32] hLp_getWriteBuffer
[ 45] hLp_produce
[ 51] hLp_getReadBuffer
[ 63] hLp_consume
[ 6f] hLp_reset
[ 79] memset
[ 80] mp_init
[ 88] mp_free
[ 90] mp_freeMessage
[ 9f] mp_addMessage
[ ad] msg_copyToBuffer
[ be] sPhasor_init
[ cb] sPhasor_onMessage
[ dd] sPhasor_k_init
[ ec] sPhasor_k_onMessage
[ 100] hv_getSampleRate
[ 111] hv_string_to_hash
[ 123] strlen
[ 12a] msg_initWithFloat
[ 13c] msg_initWithBang
[ 14d] msg_initWithSymbol
[ 160] msg_initWithHash
[ 171] memcpy
[ 178] strncpy
[ 180] msg_compareSymbol
[ 192] strcmp
[ 199] msg_equalsElement
[ 1ab] msg_setElementToFrom
[ 1c0] snprintf
[ 1c9] mq_initWithPoolSize
[ 1dd] mq_size
[ 1e5] mq_addMessage
[ 1f3] mq_addMessageByTimestamp
[ 20c] mq_pop
[ 213] mq_removeMessage
[ 224] mq_clear
[ 22d] mq_free
[ 235] mq_clearAfter
[ 243] hTable_init
[ 24f] hTable_initWithData
[ 263] hTable_initWithFinalData
[ 27c] hTable_free
[ 288] hTable_resize
[ 296] realloc
[ 29e] hTable_onMessage
[ 2af] ceilf
[ 2b5] unit_init
[ 2bf] unit_teardown
[ 2cd] unit_reset
[ 2d8] unit_resume
[ 2e4] unit_suspend
[ 2f1] unit_render
[ 2fd] unit_get_param_value
[ 312] unit_get_param_str_value
[ 32b] unit_set_param_value
[ 340] unit_set_tempo
[ 34f] unit_tempo_4ppqn_tick
[ 365] unit_note_on
[ 372] unit_note_off
[ 380] unit_all_note_off
[ 392] unit_pitch_bend
[ 3a2] unit_channel_pressure
[ 3b8] unit_aftertouch
[ 3c8] hv_heavy_new
[ 3d5] hv_getNumOutputChannels
[ 3ed] hv_sendFloatToReceiver
[ 404] hv_processInline
[ 415] midi_to_hz_lut_f
[ 426] _ZN12HeavyContext7getSizeEv
[ 442] _ZN12HeavyContext13getSampleRateEv
[ 465] _ZN12HeavyContext16getCurrentSampleEv
[ 48b] _ZN12HeavyContext21samplesToMillisecondsEm
[ 4b6] _ZN12HeavyContext11setUserDataEPv
[ 4d8] _ZN12HeavyContext11getUserDataEv
[ 4f9] _ZN12HeavyContext11setSendHookEPFvP21HeavyContextInterfacePKcmPK9HvMessageE
[ 545] _ZN12HeavyContext11getSendHookEv
[ 566] _ZN12HeavyContext12setPrintHookEPFvP21HeavyContextInterfacePKcS3_PK9HvMessageE
[ 5b5] _ZN12HeavyContext12getPrintHookEv
[ 5d7] _ZN12HeavyContext17getBufferForTableEm
[ 5fe] _ZN12HeavyContext17getLengthForTableEm
[ 625] _Z15defaultSendHookP21HeavyContextInterfacePKcmPK9HvMessage
[ 661] _ZN12HeavyContext11lockReleaseEv
[ 682] _ZN12HeavyContext24setInputMessageQueueSizeEi
[ 6b0] _ZN12HeavyContext25setOutputMessageQueueSizeEi
[ 6df] _ZN12HeavyContext18sendBangToReceiverEm
[ 707] _ZN12HeavyContext19sendFloatToReceiverEmf
[ 731] _ZN12HeavyContext20sendSymbolToReceiverEmPKc
[ 75e] _ZN12HeavyContext22sendMessageToReceiverVEmdPKcz
[ 78f] fmax
[ 794] _ZN12HeavyContext11lockAcquireEv
[ 7b5] _ZN12HeavyContext7lockTryEv
[ 7d1] _ZN12HeavyContext13cancelMessageEP9HvMessagePFvP21HeavyContextInterfaceiPKS0_E
[ 820] _ZN12HeavyContext17setLengthForTableEmm
[ 848] _ZN12HeavyContext18getNextSentMessageEPmP9HvMessagej
[ 87d] _ZN12HeavyContext21millisecondsToSamplesEf
[ 8a8] fmaxf
[ 8ae] _ZN12HeavyContext21sendMessageToReceiverEmdP9HvMessage
[ 8e5] _ZN12HeavyContextC2Ediii
[ 8fe] _ZTV12HeavyContext
[ 911] _ZN12HeavyContextC1Ediii
[ 92a] _ZN12HeavyContextD2Ev
[ 940] _ZN12HeavyContextD1Ev
[ 956] _ZN12HeavyContextD0Ev
[ 96c] _ZdlPv
[ 973] _ZN12HeavyContext24scheduleMessageForObjectEPK9HvMessagePFvP21HeavyContextInterfaceiS2_Ei
[ 9cd] _ZN12HeavyContext16getHashForStringEPKc
[ 9f5] _Z13_hv_table_getP21HeavyContextInterfacem
[ a20] _Z30_hv_scheduleMessageForReceiverP21HeavyContextInterfacemP9HvMessage
[ a67] _Z28_hv_scheduleMessageForObjectP21HeavyContextInterfacePK9HvMessagePFvS0_iS3_Ei
[ ab8] hv_table_get
[ ac5] hv_scheduleMessageForReceiver
[ ae3] hv_scheduleMessageForObject
[ aff] __cxa_pure_virtual
[ b12] _ZN11Heavy_heavy7getNameEv
[ b2d] _ZN11Heavy_heavy19getNumInputChannelsEv
[ b55] _ZN11Heavy_heavy20getNumOutputChannelsEv
[ b7e] _ZN11Heavy_heavy15getTableForHashEm
[ ba2] _ZN11Heavy_heavy13processInlineEPfS0_i
[ bc9] _ZN11Heavy_heavy24processInlineInterleavedEPfS0_i
[ bfb] _ZN11Heavy_heavyD2Ev
[ c10] _ZTV11Heavy_heavy
[ c22] _ZN11Heavy_heavyD1Ev
[ c37] _ZN11Heavy_heavyD0Ev
[ c4c] _ZN11Heavy_heavy29cReceive_5CyYSrdm_sendMessageEP21HeavyContextInterfaceiPK9HvMessage
[ ca2] _ZN11Heavy_heavy7processEPPfS1_i
[ cc3] _ZN11Heavy_heavy16getParameterInfoEiP15HvParameterInfo
[ cfa] _ZN11Heavy_heavy26scheduleMessageForReceiverEmP9HvMessage
[ d34] hv_heavy_free
[ d42] _ZN11Heavy_heavyC2Ediii
[ d5a] _ZN11Heavy_heavyC1Ediii
[ d72] hv_heavy_new_with_options
[ d8c] hv_table_setLength
[ d9f] hv_table_getBuffer
[ db2] hv_table_getLength
[ dc5] hv_msg_getByteSize
[ dd8] hv_msg_init
[ de4] hv_msg_getNumElements
[ dfa] hv_msg_getTimestamp
[ e0e] hv_msg_setTimestamp
[ e22] hv_msg_isBang
[ e30] hv_msg_setBang
[ e3f] hv_msg_isFloat
[ e4e] hv_msg_getFloat
[ e5e] hv_msg_setFloat
[ e6e] hv_msg_isSymbol
[ e7e] hv_msg_getSymbol
[ e8f] hv_msg_setSymbol
[ ea0] hv_msg_isHash
[ eae] hv_msg_getHash
[ ebd] hv_msg_hasFormat
[ ece] hv_msg_toString
[ ede] hv_msg_copy
[ eea] hv_msg_free
[ ef6] hv_getSize
[ f01] hv_getNumInputChannels
[ f18] hv_setPrintHook
[ f28] hv_getPrintHook
[ f38] hv_setSendHook
[ f47] hv_stringToHash
[ f57] hv_sendBangToReceiver
[ f6d] hv_sendSymbolToReceiver
[ f85] hv_sendMessageToReceiverV
[ f9f] hv_sendMessageToReceiverFF
[ fba] hv_sendMessageToReceiverFFF
[ fd6] hv_sendMessageToReceiver
[ fef] hv_cancelMessage
[ 1000] hv_getName
[ 100b] hv_setUserData
[ 101a] hv_getUserData
[ 1029] hv_getCurrentTime
[ 103b] hv_getCurrentSample
[ 104f] hv_samplesToMilliseconds
[ 1068] hv_millisecondsToSamples
[ 1081] hv_getParameterInfo
[ 1095] hv_lock_acquire
[ 10a5] hv_lock_try
[ 10b1] hv_lock_release
[ 10c1] hv_setInputMessageQueueSize
[ 10dd] hv_setOutputMessageQueueSize
[ 10fa] hv_getNextSentMessage
[ 1110] hv_process
[ 111b] hv_processInlineInterleaved
[ 1137] hv_delete
[ 1141] _malloc_r
[ 114b] _free_r
[ 1153] __malloc_lock
[ 1161] __malloc_unlock
[ 1171] __malloc_free_list
[ 1184] _sbrk_r
[ 118c] __malloc_sbrk_start
[ 11a0] _realloc_r
[ 11ab] _sbrk
[ 11b1] errno
[ 11b7] _global_impure_ptr
[ 11ca] __sf_fake_stdin
[ 11da] __sf_fake_stdout
[ 11eb] __sf_fake_stderr
[ 11fc] __retarget_lock_acquire_recursive
[ 121e] __lock___malloc_recursive_mutex
[ 123e] __retarget_lock_release_recursive
[ 1260] _malloc_usable_size_r
[ 1276] cleanup_glue
[ 1283] _reclaim_reent
[ 1292] __retarget_lock_init
[ 12a7] __retarget_lock_init_recursive
[ 12c6] __retarget_lock_close
[ 12dc] __retarget_lock_close_recursive
[ 12fc] __retarget_lock_acquire
[ 1314] __retarget_lock_try_acquire
[ 1330] __retarget_lock_try_acquire_recursive
[ 1356] __retarget_lock_release
[ 136e] __lock___arc4random_mutex
[ 1388] __lock___dd_hash_mutex
[ 139f] __lock___tz_mutex
[ 13b1] __lock___env_recursive_mutex
[ 13ce] __lock___at_quick_exit_mutex
[ 13eb] __lock___atexit_recursive_mutex
[ 140b] __lock___sfp_recursive_mutex
[ 1428] __lock___sinit_recursive_mutex
[ 1447] __fpclassifyd
[ 1455] __fpclassifyf
[ 1463] _ZSt9terminatev
[ 1473] __cxa_deleted_virtual
[ 1489] _ZN10__cxxabiv111__terminateEPFvvE
[ 14ac] abort
[ 14b2] _ZSt13set_terminatePFvvE
[ 14cb] _ZN10__cxxabiv119__terminate_handlerE
[ 14f1] _ZSt13get_terminatev
[ 1506] _ZN10__cxxabiv112__unexpectedEPFvvE
[ 152a] _ZSt14set_unexpectedPFvvE
[ 1544] _ZN10__cxxabiv120__unexpected_handlerE
[ 156b] _ZSt14get_unexpectedv
[ 1581] _ZSt10unexpectedv
[ 1593] raise
[ 1599] _exit
[ 159f] _init_signal_r
[ 15ae] _raise_r
[ 15b7] _getpid_r
[ 15c1] _kill_r
[ 15c9] __sigtramp_r
[ 15d6] _init_signal
[ 15e3] __sigtramp
[ 15ee] _kill
[ 15f4] _getpid
[ 15fc] __heap_size
[ 1608] __stack_size
[ 1615] __data_start__
[ 1624] __data_end__
[ 1631] __bss_start__
[ 163f] __bss_end__
[ 164b] __HEAP
[ 1652] __HEAP_END
[ 165d] _stack_end
[ 1668] _stack_addr
[ 1674] __stack
[ 167c] __SP_INIT
この結果を見ると、hvccが生成したコードで使用されているシンボルが沢山含まれています。
実際には、hvccが生成した関数や変数はユニットの外部から直接アクセスされないので、テーブルに載せておく必要はありません。ユニットの外部からアクセスされる関数・変数は、unit_
で始まるものだけのはずです。
gccには、-fvisibility=hidden
というオプションがあり、これを使えばextern宣言していないシンボルはテーブルに載らなくなります。これをconfig.mk
のUDEFS
に追加して試してみます。
$ size ./sawosc.nts1mkiiunit
text data bss dec hex filename
18151 760 92 19003 4a3b ./sawosc.nts1mkiiunit
サイズが大幅に減りました。ただ、これをNTS-1 mkIIに転送するためにKONTROL Editorにドラッグ&ドロップすると、KONGROL Editorが落ちてしまいます。改めてシンボルテーブルを調べてみると、外部から呼び出される、unit_
で始まるシンボルまで消えてしまっていました。
対処としては、テーブルに残したい関数や変数にはvisibility("default")
というアトリビュートを付けます。このアトリビュートが付いたシンボルは-fvisibility=hidden
の効果を逃れます。
logue SDKの構造上、このアトリビュートはattributes.hの中で#define
されているマクロに、以下のような感じで追加するのが良さそうです。
common/attributes.h:
#define __unit_callback __attribute__((used, visibility("default")))
#define __unit_header __attribute__((used, section(".unit_header") ,visibility("default")))
ここからだんだん話がややこしくなってくるのですが、いろいろ実験してみた限りでは、このアトリビュートはソースファイルでの実装だけでなく、ヘッダファイルでの宣言にもつける必要があるようです。
たとえば、unit_で始まる関数は、ユニットのプロジェクトごとのunit.cc
の中で実装されています。
一方、関数の宣言はSDKのフォルダ(logue-sdk/platforms/nts-1_mkii/common
)内のヘッダファイルで行われています。具体的には、common/unit.h
が関数の宣言、common/_unit_base.c
が__attribute__((weak))
を使ったデフォルト実装となっています。
ところが、common/unit.h
では、各関数を__unit_header
無しで宣言しています。そのため、attributes.hで追加したvisibility("default")
は適用されません。関数を実装するunit.ccの中で、これらの関数をvisibility("default")
付きで実装しても、アトリビュートなしのヘッダファイルの宣言のほうが優先されてしまいます。従って、visibility("default")
をunit_で始まる関数群に適用するためには、common/unit.h
の中でのこれらの関数の宣言に__unit_header
を追加することが必要になります。
まとめると、visibility("default")
を有効にするには、
・attributes.h の__unit_header
マクロにvisibility("default")
を追加する
・unit.h の各関数の宣言に__unit_header
を追加する
ことが必要です。
SDKの複数のファイルに変更が必要になるので、common/
配下で共有されているファイルそのものを変更するか、個別のユニットのディレクトリ内のファイルの変更で対応するか、悩ましいところです。今回はひとまずユニット内のファイルの変更だけで対応してみます。
それには、
・unit.hとattributes.hをプロジェクトのディレクトリにコピーして変更
・unit.hを必要とするソースコード(少なくとも、unit.cc、osc.h、header.c、それに_unit_base.c)が変更後のunit.hを読むようにする
必要があります。
以下、実際の作業です。
まず、変更しなければならないSDKのファイルをプロジェクトのディレクトリにコピーしてきます。目印としてファイル名の最後に”-v”を付けておきます。
$ cp ../common/attributes.h ./attributes-v.h
$ cp ../common-ex/unit.h ./unit-v.h
$ cp ../common/_unit_base.c ./_unit_base-v.c
そして、これらを書き換えます。以下は書き換えた結果のファイルです。
attributes-v.h
unit-v.h
また、header.c、unit.cc、osc.hは、以下のようにunit-v.hをunit_osc.hよりも先にincludeするように書き換えます。unit_osc.hは(オリジナルの)unit.hをincludeしていますが、先にunit-v.hが読み込まれていれば#ifndef UNIT_H_
が成立しないのでunit.hの中身は読み込まれません。
#include "unit-v.h"
#include "unit_osc.h"
_unit_base-v.cは、unit.hの代わりにunit-v.hをincludeするように書き換えます。(実際には、_unit_base.cで定義されている関数はすべてunit.ccにある定義で置き換えられるはずですが、unit.hが残っているのは気持ち悪いので。)(
さらに、Makefileの中で、以下のように、_unit_base.cの代わりに_unit_base-v.cを使うように書き換えます。
CSRC := $(UCSRC)
#CSRC += $(realpath $(COMMON_SRC_PATH)/_unit_base.c)
CSRC += $(realpath $(COMMON_SRC_PATH)/_unit_base-v.c)
これで、ようやく準備が整いました。
ビルドしてみると10KB弱の領域が解放されました。今回は元のPure Dataパッチがシンプルでしたが、より複雑なパッチならさらに効果は大きくなるでしょう。
$ size -A ./sawosc.nts1mkiiunit
./sawosc.nts1mkiiunit :
section size addr
.hash 852 212
.dynsym 1824 1064
.dynstr 1762 2888
.rel.plt 336 5500
.text 11748 5840
.plt 688 17588
.unit_header 408 18280
.preinit_array 0 18688
.init_array 0 18688
.dynamic 152 18688
.got 188 18840
.data 420 19028
.bss 88 19448
.stack 4 19536
.comment 73 0
.ARM.attributes 50 0
.rel.dyn 848 4652
Total 19441
下のグラフはbefore-afterの比較です。セクションのサイズの順にソートしています。最大を占めるのはもちろんコードですが、それに次ぐサイズだった動的リンク関連のデータが大幅に減っているのが分かると思います。
今回のシンプルなPure Dataパッチでは、Cに変換してビルドした結果のバイナリサイズが16KBを超えているので、モジュレーションユニットを作ることは難しそうです。ディレイやリバーブでは、SDRAM領域をどうやってPure Dataパッチから使うかという別の課題がありますが、それはまた別途検討したいと思います。NTS-3では、ユニットのフットプリント制約は24Kbなので、簡易的なものなら作れるかもしれません。
コメント